diff --git a/README.md b/README.md index ac09c41..725772b 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,6 @@ Szkielet komponentu Joomla 6 do gromadzenia pytań testowych. ## Instalacja -W Joomla wybierz **System → Install → Install from Folder** i wskaż ten katalog. Manifestem instalacyjnym jest `com_testquestions.xml` (wersja 1.43.7). +W Joomla wybierz **System → Install → Install from Folder** i wskaż ten katalog. Manifestem instalacyjnym jest `com_testquestions.xml` (wersja 1.43.8). Po instalacji komponent jest dostępny w panelu administratora jako **Pytania testowe**. diff --git a/administrator/components/com_testquestions/language/en-GB/com_testquestions.ini b/administrator/components/com_testquestions/language/en-GB/com_testquestions.ini index d7c5bc0..bf70c08 100644 --- a/administrator/components/com_testquestions/language/en-GB/com_testquestions.ini +++ b/administrator/components/com_testquestions/language/en-GB/com_testquestions.ini @@ -70,18 +70,13 @@ COM_TESTQUESTIONS_AI_GROK_KEY="Klucz API Grok" COM_TESTQUESTIONS_AI_OPENAI_MODEL="Model OpenAI" COM_TESTQUESTIONS_AI_GROK_MODEL="Model Grok" COM_TESTQUESTIONS_AI_MODEL_INVALID="Wybierz prawidłowy model dla każdego dostawcy AI." -COM_TESTQUESTIONS_AI_BALANCE="Saldo kont API" -COM_TESTQUESTIONS_AI_OPENAI_BALANCE_INFO="OpenAI nie udostępnia salda przedpłaconych środków przez zwykłe API. Saldo sprawdzisz w panelu rozliczeń: " -COM_TESTQUESTIONS_AI_OPENAI_BILLING_LINK="Otwórz rozliczenia OpenAI" -COM_TESTQUESTIONS_AI_GROK_TEAM_ID="ID zespołu xAI" -COM_TESTQUESTIONS_AI_GROK_MANAGEMENT_KEY="Klucz zarządzający xAI" -COM_TESTQUESTIONS_AI_GROK_MANAGEMENT_KEY_SAVED="Klucz zarządzający jest zapisany. Pozostaw pole bez zmian, aby go zachować." -COM_TESTQUESTIONS_AI_GROK_MANAGEMENT_KEY_INFO="Wymagany jest osobny Management API key z xAI Console → Settings → Management Keys." -COM_TESTQUESTIONS_AI_CHECK_GROK_BALANCE="Sprawdź saldo xAI" -COM_TESTQUESTIONS_AI_GROK_BALANCE_INFO="Odczyt dotyczy dostępnych kredytów przedpłaconych zespołu xAI. Nie obejmuje limitu rozliczeń postpaid." -COM_TESTQUESTIONS_AI_GROK_BALANCE_CONFIG_REQUIRED="Aby sprawdzić saldo xAI, wpisz ID zespołu i klucz zarządzający, a następnie ponów próbę." -COM_TESTQUESTIONS_AI_GROK_BALANCE_FAILED="Nie udało się odczytać salda xAI. Sprawdź ID zespołu, klucz zarządzający i jego uprawnienia." -COM_TESTQUESTIONS_AI_GROK_BALANCE_RESULT="Dostępne kredyty przedpłacone xAI: $%s USD" +COM_TESTQUESTIONS_AI_TOKEN_USAGE="Wykorzystanie tokenów API" +COM_TESTQUESTIONS_AI_TOKEN_USAGE_INFO="Suma użycia zapisanego przy ocenach AI we wszystkich wynikach testów. Powtórne oceny są zliczane, jeśli zostały wykonane po tej aktualizacji." +COM_TESTQUESTIONS_AI_USAGE_CALLS="Liczba ocen" +COM_TESTQUESTIONS_AI_USAGE_INPUT="Tokeny wejściowe" +COM_TESTQUESTIONS_AI_USAGE_OUTPUT="Tokeny wyjściowe" +COM_TESTQUESTIONS_AI_USAGE_TOTAL="Razem tokenów" +COM_TESTQUESTIONS_AI_USAGE_COST="Koszt zapisany" COM_TESTQUESTIONS_AI_SETTINGS_MENU="Konfiguracja AI" COM_TESTQUESTIONS_AI_SETTINGS_SAVED="Ustawienia AI zapisano." COM_TESTQUESTIONS_AI_SAVE_FAILED="Nie udało się zapisać ustawień AI." diff --git a/administrator/components/com_testquestions/sql/updates/mysql/1.43.8.sql b/administrator/components/com_testquestions/sql/updates/mysql/1.43.8.sql new file mode 100644 index 0000000..50926ad --- /dev/null +++ b/administrator/components/com_testquestions/sql/updates/mysql/1.43.8.sql @@ -0,0 +1,5 @@ +UPDATE `#__extensions` +SET `params` = JSON_REMOVE(`params`, '$.ai_grok_team_id', '$.ai_grok_management_key') +WHERE `type` = 'component' + AND `element` = 'com_testquestions' + AND JSON_VALID(`params`); diff --git a/administrator/components/com_testquestions/src/Controller/AiController.php b/administrator/components/com_testquestions/src/Controller/AiController.php index dfc5dd5..ab4dea5 100644 --- a/administrator/components/com_testquestions/src/Controller/AiController.php +++ b/administrator/components/com_testquestions/src/Controller/AiController.php @@ -5,7 +5,6 @@ namespace Komponent\Component\Testquestions\Administrator\Controller; defined('_JEXEC') or die; use Joomla\CMS\Factory; -use Joomla\CMS\Http\HttpFactory; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Controller\BaseController; use Joomla\CMS\Router\Route; @@ -24,55 +23,6 @@ class AiController extends BaseController ); } - public function checkbalance(): void - { - $settings = $this->saveSettings(); - $teamId = trim((string) ($settings['ai_grok_team_id'] ?? '')); - $managementKey = trim((string) ($settings['ai_grok_management_key'] ?? '')); - - if ($teamId === '' || $managementKey === '') { - $this->setRedirect( - Route::_('index.php?option=com_testquestions&view=ai', false), - Text::_('COM_TESTQUESTIONS_AI_GROK_BALANCE_CONFIG_REQUIRED'), - 'warning' - ); - return; - } - - try { - $response = HttpFactory::getHttp()->get( - 'https://management-api.x.ai/v1/billing/teams/' . rawurlencode($teamId) . '/prepaid/balance', - ['Authorization' => 'Bearer ' . $managementKey, 'Accept' => 'application/json'], - 30 - ); - } catch (\Throwable $error) { - $this->setRedirect( - Route::_('index.php?option=com_testquestions&view=ai', false), - Text::_('COM_TESTQUESTIONS_AI_GROK_BALANCE_FAILED'), - 'error' - ); - return; - } - - $body = json_decode((string) $response->body, true); - if ((int) $response->code < 200 || (int) $response->code >= 300 || !isset($body['total']['val']) || !is_numeric($body['total']['val'])) { - $this->setRedirect( - Route::_('index.php?option=com_testquestions&view=ai', false), - Text::_('COM_TESTQUESTIONS_AI_GROK_BALANCE_FAILED'), - 'error' - ); - return; - } - - $balance = abs((float) $body['total']['val']) / 100; - $message = Text::sprintf('COM_TESTQUESTIONS_AI_GROK_BALANCE_RESULT', number_format($balance, 2, ',', ' ')); - $this->setRedirect( - Route::_('index.php?option=com_testquestions&view=ai', false), - $message, - 'message' - ); - } - private function saveSettings(): array { if (!Session::checkToken()) { @@ -132,15 +82,8 @@ class AiController extends BaseController } } - $teamId = trim((string) $input->post->get('ai_grok_team_id', '', 'STRING')); - if ($teamId !== '') { - $settings['ai_grok_team_id'] = $teamId; - } - $managementKey = trim((string) $input->post->get('ai_grok_management_key', '', 'RAW')); - if ($managementKey !== '') { - $settings['ai_grok_management_key'] = $managementKey; - } unset($settings['ai_api_key']); + unset($settings['ai_grok_team_id'], $settings['ai_grok_management_key']); $extension = (object) [ 'extension_id' => (int) $row->extension_id, diff --git a/administrator/components/com_testquestions/src/Model/ResultModel.php b/administrator/components/com_testquestions/src/Model/ResultModel.php index c549a0b..2b5b84d 100644 --- a/administrator/components/com_testquestions/src/Model/ResultModel.php +++ b/administrator/components/com_testquestions/src/Model/ResultModel.php @@ -161,6 +161,14 @@ class ResultModel extends BaseDatabaseModel $item->answers[$questionId]['ai_feedback'] = $aiResult['feedback']; $item->answers[$questionId]['awarded_points'] = $aiResult['awarded_points']; if ($aiResult['usage'] !== null) { + $history = $item->answers[$questionId]['ai_usage_history'] ?? null; + if (!is_array($history)) { + $history = !empty($item->answers[$questionId]['ai_usage']) && is_array($item->answers[$questionId]['ai_usage']) + ? [$item->answers[$questionId]['ai_usage']] + : []; + } + $history[] = $aiResult['usage']; + $item->answers[$questionId]['ai_usage_history'] = $history; $item->answers[$questionId]['ai_usage'] = $aiResult['usage']; } diff --git a/administrator/components/com_testquestions/src/View/Ai/HtmlView.php b/administrator/components/com_testquestions/src/View/Ai/HtmlView.php index 95ffb1f..4aa02e2 100644 --- a/administrator/components/com_testquestions/src/View/Ai/HtmlView.php +++ b/administrator/components/com_testquestions/src/View/Ai/HtmlView.php @@ -12,6 +12,7 @@ use Joomla\CMS\Toolbar\ToolbarHelper; class HtmlView extends BaseHtmlView { protected array $settings = []; + protected array $usageTotals = []; public function display($tpl = null) { @@ -24,6 +25,54 @@ class HtmlView extends BaseHtmlView ->where($db->quoteName('element') . ' = ' . $db->quote('com_testquestions')) )->loadResult(); $this->settings = json_decode($raw, true) ?: []; + $this->usageTotals = [ + 'openai' => ['calls' => 0, 'input_tokens' => 0, 'output_tokens' => 0, 'total_tokens' => 0, 'cost_usd' => 0.0, 'cost_calls' => 0], + 'grok' => ['calls' => 0, 'input_tokens' => 0, 'output_tokens' => 0, 'total_tokens' => 0, 'cost_usd' => 0.0, 'cost_calls' => 0], + ]; + + $usageRows = $db->setQuery( + $db->getQuery(true) + ->select($db->quoteName('answers')) + ->from($db->quoteName('#__testquestions_results')) + ->where($db->quoteName('answers') . ' LIKE ' . $db->quote('%ai_usage%')) + )->loadColumn(); + + foreach ($usageRows as $answersJson) { + $answers = json_decode((string) $answersJson, true); + if (!is_array($answers)) { + continue; + } + + foreach ($answers as $answer) { + if (!is_array($answer)) { + continue; + } + + $usages = $answer['ai_usage_history'] ?? (isset($answer['ai_usage']) ? [$answer['ai_usage']] : []); + if (!is_array($usages)) { + continue; + } + + foreach ($usages as $usage) { + if (!is_array($usage)) { + continue; + } + $provider = (string) ($usage['provider'] ?? ''); + if (!isset($this->usageTotals[$provider])) { + continue; + } + + $this->usageTotals[$provider]['calls']++; + $this->usageTotals[$provider]['input_tokens'] += (int) ($usage['input_tokens'] ?? 0); + $this->usageTotals[$provider]['output_tokens'] += (int) ($usage['output_tokens'] ?? 0); + $this->usageTotals[$provider]['total_tokens'] += (int) ($usage['total_tokens'] ?? 0); + if (is_numeric($usage['cost_usd'] ?? null)) { + $this->usageTotals[$provider]['cost_usd'] += (float) $usage['cost_usd']; + $this->usageTotals[$provider]['cost_calls']++; + } + } + } + } ToolbarHelper::title(Text::_('COM_TESTQUESTIONS_AI_SETTINGS')); ToolbarHelper::link('index.php?option=com_testquestions&view=questions', 'COM_TESTQUESTIONS_QUESTIONS', 'arrow-left'); diff --git a/administrator/components/com_testquestions/tmpl/ai/default.php b/administrator/components/com_testquestions/tmpl/ai/default.php index d61d1e4..f977991 100644 --- a/administrator/components/com_testquestions/tmpl/ai/default.php +++ b/administrator/components/com_testquestions/tmpl/ai/default.php @@ -2,6 +2,7 @@ +usageTotals; ?> settings['ai_provider'] ?? 'openai'); ?> settings['ai_api_key'] ?? ''); ?> settings['ai_openai_api_key'] ?? ($provider === 'openai' ? $legacyKey : '')); ?> @@ -55,29 +56,35 @@
-

-
- - +

+

+
+ + + + + + + + + + + + + 'OpenAI', 'grok' => 'Grok'] as $usageProvider => $usageProviderLabel) : ?> + + + + + + + + + + + +
0 ? number_format($providerUsage['cost_usd'], 8, ',', ' ') . ' USD' : '—'; ?>
-
- - -
-
- -
- - -
- - settings['ai_grok_management_key']) ? \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_AI_GROK_MANAGEMENT_KEY_SAVED') : \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_AI_GROK_MANAGEMENT_KEY_INFO'); ?> - -
- -