Compare commits

..
4 Commits
Author SHA1 Message Date
Wojtek bf05c9515c Split AI token counters by provider 2026-10-16 19:48:45 +02:00
Wojtek 08220dc6e8 Add aggregate AI tokens and move column 2026-10-16 19:46:10 +02:00
Wojtek 42b5006574 Show AI token usage per saved test 2026-10-16 19:43:15 +02:00
Wojtek 8693720b78 Replace AI balance with usage counters 2026-10-16 19:36:42 +02:00
14 changed files with 242 additions and 94 deletions
+1 -1
View File
@@ -30,6 +30,6 @@ Szkielet komponentu Joomla 6 do gromadzenia pytań testowych.
## Instalacja ## 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.11).
Po instalacji komponent jest dostępny w panelu administratora jako **Pytania testowe**. Po instalacji komponent jest dostępny w panelu administratora jako **Pytania testowe**.
@@ -70,18 +70,13 @@ COM_TESTQUESTIONS_AI_GROK_KEY="Klucz API Grok"
COM_TESTQUESTIONS_AI_OPENAI_MODEL="Model OpenAI" COM_TESTQUESTIONS_AI_OPENAI_MODEL="Model OpenAI"
COM_TESTQUESTIONS_AI_GROK_MODEL="Model Grok" COM_TESTQUESTIONS_AI_GROK_MODEL="Model Grok"
COM_TESTQUESTIONS_AI_MODEL_INVALID="Wybierz prawidłowy model dla każdego dostawcy AI." COM_TESTQUESTIONS_AI_MODEL_INVALID="Wybierz prawidłowy model dla każdego dostawcy AI."
COM_TESTQUESTIONS_AI_BALANCE="Saldo kont API" COM_TESTQUESTIONS_AI_TOKEN_USAGE="Wykorzystanie tokenów 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_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_OPENAI_BILLING_LINK="Otwórz rozliczenia OpenAI" COM_TESTQUESTIONS_AI_USAGE_CALLS="Liczba ocen"
COM_TESTQUESTIONS_AI_GROK_TEAM_ID="ID zespołu xAI" COM_TESTQUESTIONS_AI_USAGE_INPUT="Tokeny wejściowe"
COM_TESTQUESTIONS_AI_GROK_MANAGEMENT_KEY="Klucz zarządzający xAI" COM_TESTQUESTIONS_AI_USAGE_OUTPUT="Tokeny wyjściowe"
COM_TESTQUESTIONS_AI_GROK_MANAGEMENT_KEY_SAVED="Klucz zarządzający jest zapisany. Pozostaw pole bez zmian, aby go zachować." COM_TESTQUESTIONS_AI_USAGE_TOTAL="Razem tokenów"
COM_TESTQUESTIONS_AI_GROK_MANAGEMENT_KEY_INFO="Wymagany jest osobny Management API key z xAI Console → Settings → Management Keys." COM_TESTQUESTIONS_AI_USAGE_COST="Koszt zapisany"
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_SETTINGS_MENU="Konfiguracja AI" COM_TESTQUESTIONS_AI_SETTINGS_MENU="Konfiguracja AI"
COM_TESTQUESTIONS_AI_SETTINGS_SAVED="Ustawienia AI zapisano." COM_TESTQUESTIONS_AI_SETTINGS_SAVED="Ustawienia AI zapisano."
COM_TESTQUESTIONS_AI_SAVE_FAILED="Nie udało się zapisać ustawień AI." COM_TESTQUESTIONS_AI_SAVE_FAILED="Nie udało się zapisać ustawień AI."
@@ -111,6 +106,8 @@ COM_TESTQUESTIONS_RESULTS="Zapisane testy"
COM_TESTQUESTIONS_RESULT_PREVIEW="Podgląd zapisanego testu" COM_TESTQUESTIONS_RESULT_PREVIEW="Podgląd zapisanego testu"
COM_TESTQUESTIONS_RESULT_NOT_FOUND="Nie znaleziono zapisanego testu." COM_TESTQUESTIONS_RESULT_NOT_FOUND="Nie znaleziono zapisanego testu."
COM_TESTQUESTIONS_RESULT_PARTICIPANT="Użytkownik" COM_TESTQUESTIONS_RESULT_PARTICIPANT="Użytkownik"
COM_TESTQUESTIONS_RESULT_AI_TOKENS="Tokeny AI"
COM_TESTQUESTIONS_RESULTS_AI_TOKENS_TOTAL="Łączne wykorzystanie tokenów w wynikach"
COM_TESTQUESTIONS_RESULT_SCORE="Wynik" COM_TESTQUESTIONS_RESULT_SCORE="Wynik"
COM_TESTQUESTIONS_RESULT_POINTS_BEFORE_AFTER="Punkty przed / po korekcie" COM_TESTQUESTIONS_RESULT_POINTS_BEFORE_AFTER="Punkty przed / po korekcie"
COM_TESTQUESTIONS_RESULT_GRADE="Ocena" COM_TESTQUESTIONS_RESULT_GRADE="Ocena"
@@ -0,0 +1 @@
-- No database changes required for version 1.43.10.
@@ -0,0 +1 @@
-- No database changes required for version 1.43.11.
@@ -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`);
@@ -0,0 +1 @@
-- No database changes required for version 1.43.9.
@@ -5,7 +5,6 @@ namespace Komponent\Component\Testquestions\Administrator\Controller;
defined('_JEXEC') or die; defined('_JEXEC') or die;
use Joomla\CMS\Factory; use Joomla\CMS\Factory;
use Joomla\CMS\Http\HttpFactory;
use Joomla\CMS\Language\Text; use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\BaseController; use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Router\Route; 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 private function saveSettings(): array
{ {
if (!Session::checkToken()) { 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_api_key']);
unset($settings['ai_grok_team_id'], $settings['ai_grok_management_key']);
$extension = (object) [ $extension = (object) [
'extension_id' => (int) $row->extension_id, 'extension_id' => (int) $row->extension_id,
@@ -161,6 +161,14 @@ class ResultModel extends BaseDatabaseModel
$item->answers[$questionId]['ai_feedback'] = $aiResult['feedback']; $item->answers[$questionId]['ai_feedback'] = $aiResult['feedback'];
$item->answers[$questionId]['awarded_points'] = $aiResult['awarded_points']; $item->answers[$questionId]['awarded_points'] = $aiResult['awarded_points'];
if ($aiResult['usage'] !== null) { 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']; $item->answers[$questionId]['ai_usage'] = $aiResult['usage'];
} }
@@ -9,6 +9,107 @@ use Joomla\CMS\MVC\Model\ListModel;
class ResultsModel extends ListModel class ResultsModel extends ListModel
{ {
public function getItems()
{
$items = parent::getItems();
foreach ($items as $item) {
$item->ai_input_tokens = 0;
$item->ai_output_tokens = 0;
$item->ai_total_tokens = 0;
$item->ai_cost_usd = 0.0;
$item->ai_tokens_by_provider = ['openai' => 0, 'grok' => 0];
$answers = json_decode((string) ($item->answers ?? ''), 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;
}
$item->ai_input_tokens += (int) ($usage['input_tokens'] ?? 0);
$item->ai_output_tokens += (int) ($usage['output_tokens'] ?? 0);
$item->ai_total_tokens += (int) ($usage['total_tokens'] ?? 0);
$provider = (string) ($usage['provider'] ?? '');
if (isset($item->ai_tokens_by_provider[$provider])) {
$item->ai_tokens_by_provider[$provider] += (int) ($usage['total_tokens'] ?? 0);
}
if (is_numeric($usage['cost_usd'] ?? null)) {
$item->ai_cost_usd += (float) $usage['cost_usd'];
}
}
}
}
return $items;
}
public function getAiUsageTotals(): array
{
$db = Factory::getContainer()->get('DatabaseDriver');
$query = clone $this->getListQuery();
$query->clear('select')->clear('order')->select($db->quoteName('r.answers'));
$answersRows = $db->setQuery($query)->loadColumn();
$emptyProviderTotals = ['input_tokens' => 0, 'output_tokens' => 0, 'total_tokens' => 0, 'cost_usd' => 0.0];
$totals = [
'input_tokens' => 0,
'output_tokens' => 0,
'total_tokens' => 0,
'cost_usd' => 0.0,
'providers' => ['openai' => $emptyProviderTotals, 'grok' => $emptyProviderTotals],
];
foreach ($answersRows 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;
}
$totals['input_tokens'] += (int) ($usage['input_tokens'] ?? 0);
$totals['output_tokens'] += (int) ($usage['output_tokens'] ?? 0);
$totals['total_tokens'] += (int) ($usage['total_tokens'] ?? 0);
if (is_numeric($usage['cost_usd'] ?? null)) {
$totals['cost_usd'] += (float) $usage['cost_usd'];
}
$provider = (string) ($usage['provider'] ?? '');
if (isset($totals['providers'][$provider])) {
$totals['providers'][$provider]['input_tokens'] += (int) ($usage['input_tokens'] ?? 0);
$totals['providers'][$provider]['output_tokens'] += (int) ($usage['output_tokens'] ?? 0);
$totals['providers'][$provider]['total_tokens'] += (int) ($usage['total_tokens'] ?? 0);
if (is_numeric($usage['cost_usd'] ?? null)) {
$totals['providers'][$provider]['cost_usd'] += (float) $usage['cost_usd'];
}
}
}
}
}
return $totals;
}
protected function populateState($ordering = 'r.created', $direction = 'desc') protected function populateState($ordering = 'r.created', $direction = 'desc')
{ {
parent::populateState($ordering, $direction); parent::populateState($ordering, $direction);
@@ -12,6 +12,7 @@ use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView class HtmlView extends BaseHtmlView
{ {
protected array $settings = []; protected array $settings = [];
protected array $usageTotals = [];
public function display($tpl = null) public function display($tpl = null)
{ {
@@ -24,6 +25,54 @@ class HtmlView extends BaseHtmlView
->where($db->quoteName('element') . ' = ' . $db->quote('com_testquestions')) ->where($db->quoteName('element') . ' = ' . $db->quote('com_testquestions'))
)->loadResult(); )->loadResult();
$this->settings = json_decode($raw, true) ?: []; $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::title(Text::_('COM_TESTQUESTIONS_AI_SETTINGS'));
ToolbarHelper::link('index.php?option=com_testquestions&view=questions', 'COM_TESTQUESTIONS_QUESTIONS', 'arrow-left'); ToolbarHelper::link('index.php?option=com_testquestions&view=questions', 'COM_TESTQUESTIONS_QUESTIONS', 'arrow-left');
@@ -15,6 +15,7 @@ class HtmlView extends BaseHtmlView
protected $pagination; protected $pagination;
protected $state; protected $state;
protected $sets; protected $sets;
protected $aiUsageTotals;
public function display($tpl = null) public function display($tpl = null)
{ {
@@ -22,6 +23,7 @@ class HtmlView extends BaseHtmlView
$this->pagination = $this->get('Pagination'); $this->pagination = $this->get('Pagination');
$this->state = $this->get('State'); $this->state = $this->get('State');
$this->sets = $this->get('Sets'); $this->sets = $this->get('Sets');
$this->aiUsageTotals = $this->get('AiUsageTotals');
ToolbarHelper::title(Text::_('COM_TESTQUESTIONS_RESULTS')); ToolbarHelper::title(Text::_('COM_TESTQUESTIONS_RESULTS'));
ToolbarHelper::link('index.php?option=com_testquestions&view=sets', 'COM_TESTQUESTIONS_SETS', 'back'); ToolbarHelper::link('index.php?option=com_testquestions&view=sets', 'COM_TESTQUESTIONS_SETS', 'back');
@@ -2,6 +2,7 @@
<?php use Joomla\CMS\HTML\HTMLHelper; ?> <?php use Joomla\CMS\HTML\HTMLHelper; ?>
<?php use Joomla\CMS\Router\Route; ?> <?php use Joomla\CMS\Router\Route; ?>
<?php $availableModels = \Komponent\Component\Testquestions\Administrator\Service\AiService::availableModels(); ?> <?php $availableModels = \Komponent\Component\Testquestions\Administrator\Service\AiService::availableModels(); ?>
<?php $usageTotals = $this->usageTotals; ?>
<?php $provider = (string) ($this->settings['ai_provider'] ?? 'openai'); ?> <?php $provider = (string) ($this->settings['ai_provider'] ?? 'openai'); ?>
<?php $legacyKey = (string) ($this->settings['ai_api_key'] ?? ''); ?> <?php $legacyKey = (string) ($this->settings['ai_api_key'] ?? ''); ?>
<?php $openaiKey = (string) ($this->settings['ai_openai_api_key'] ?? ($provider === 'openai' ? $legacyKey : '')); ?> <?php $openaiKey = (string) ($this->settings['ai_openai_api_key'] ?? ($provider === 'openai' ? $legacyKey : '')); ?>
@@ -55,29 +56,35 @@
</select> </select>
</div> </div>
<hr> <hr>
<h3><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_AI_BALANCE'); ?></h3> <h3><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_AI_TOKEN_USAGE'); ?></h3>
<div class="alert alert-info"> <p class="text-muted"><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_AI_TOKEN_USAGE_INFO'); ?></p>
<?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_AI_OPENAI_BALANCE_INFO'); ?> <div class="table-responsive">
<a href="https://platform.openai.com/settings/organization/billing/overview" target="_blank" rel="noopener noreferrer"><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_AI_OPENAI_BILLING_LINK'); ?></a> <table class="table table-striped">
<thead>
<tr>
<th><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_AI_PROVIDER'); ?></th>
<th><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_AI_USAGE_CALLS'); ?></th>
<th><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_AI_USAGE_INPUT'); ?></th>
<th><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_AI_USAGE_OUTPUT'); ?></th>
<th><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_AI_USAGE_TOTAL'); ?></th>
<th><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_AI_USAGE_COST'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach (['openai' => 'OpenAI', 'grok' => 'Grok'] as $usageProvider => $usageProviderLabel) : ?>
<?php $providerUsage = $usageTotals[$usageProvider]; ?>
<tr>
<th><?php echo $usageProviderLabel; ?></th>
<td><?php echo number_format($providerUsage['calls'], 0, ',', ' '); ?></td>
<td><?php echo number_format($providerUsage['input_tokens'], 0, ',', ' '); ?></td>
<td><?php echo number_format($providerUsage['output_tokens'], 0, ',', ' '); ?></td>
<td><?php echo number_format($providerUsage['total_tokens'], 0, ',', ' '); ?></td>
<td><?php echo $providerUsage['cost_calls'] > 0 ? number_format($providerUsage['cost_usd'], 8, ',', ' ') . ' USD' : '—'; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div> </div>
<div class="mb-3">
<label for="ai-grok-team-id" class="form-label"><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_AI_GROK_TEAM_ID'); ?></label>
<input type="text" id="ai-grok-team-id" name="ai_grok_team_id" class="form-control" maxlength="255" value="<?php echo htmlspecialchars((string) ($this->settings['ai_grok_team_id'] ?? ''), ENT_QUOTES, 'UTF-8'); ?>">
</div>
<div class="mb-3">
<label for="ai-grok-management-key" class="form-label"><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_AI_GROK_MANAGEMENT_KEY'); ?></label>
<div class="input-group">
<input type="password" id="ai-grok-management-key" name="ai_grok_management_key" value="<?php echo htmlspecialchars((string) ($this->settings['ai_grok_management_key'] ?? ''), ENT_QUOTES, 'UTF-8'); ?>" class="form-control" maxlength="4096" autocomplete="new-password">
<button type="button" class="btn btn-outline-secondary" data-ai-key-toggle="ai-grok-management-key">Pokaż</button>
</div>
<small class="form-text text-muted">
<?php echo !empty($this->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'); ?>
</small>
</div>
<button type="submit" class="btn btn-outline-primary" onclick="this.form.task.value='ai.checkbalance';">
<?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_AI_CHECK_GROK_BALANCE'); ?>
</button>
<div class="form-text mt-2"><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_AI_GROK_BALANCE_INFO'); ?></div>
<hr> <hr>
<button type="submit" class="btn btn-primary" onclick="this.form.task.value='ai.save';"> <button type="submit" class="btn btn-primary" onclick="this.form.task.value='ai.save';">
<?php echo \Joomla\CMS\Language\Text::_('JTOOLBAR_SAVE'); ?> <?php echo \Joomla\CMS\Language\Text::_('JTOOLBAR_SAVE'); ?>
@@ -33,6 +33,24 @@ use Joomla\CMS\Router\Route;
</div> </div>
<div class="col-md-3 align-self-end"><button type="submit" class="btn btn-primary"><?php echo \Joomla\CMS\Language\Text::_('JSEARCH_FILTER_SUBMIT'); ?></button></div> <div class="col-md-3 align-self-end"><button type="submit" class="btn btn-primary"><?php echo \Joomla\CMS\Language\Text::_('JSEARCH_FILTER_SUBMIT'); ?></button></div>
</div> </div>
<div class="alert alert-info" role="status">
<strong><?php echo $this->escape(\Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_RESULTS_AI_TOKENS_TOTAL')); ?>:</strong>
<span class="d-block">
<strong>OpenAI:</strong>
<?php echo number_format((int) $this->aiUsageTotals['providers']['openai']['total_tokens'], 0, ',', ' '); ?> tokenów
(<?php echo number_format((int) $this->aiUsageTotals['providers']['openai']['input_tokens'], 0, ',', ' '); ?> wej. /
<?php echo number_format((int) $this->aiUsageTotals['providers']['openai']['output_tokens'], 0, ',', ' '); ?> wyj.)
<?php if ($this->aiUsageTotals['providers']['openai']['cost_usd'] > 0) : ?> · <?php echo number_format((float) $this->aiUsageTotals['providers']['openai']['cost_usd'], 8, ',', ' '); ?> USD<?php endif; ?>
</span>
<span class="d-block">
<strong>Grok:</strong>
<?php echo number_format((int) $this->aiUsageTotals['providers']['grok']['total_tokens'], 0, ',', ' '); ?> tokenów
(<?php echo number_format((int) $this->aiUsageTotals['providers']['grok']['input_tokens'], 0, ',', ' '); ?> wej. /
<?php echo number_format((int) $this->aiUsageTotals['providers']['grok']['output_tokens'], 0, ',', ' '); ?> wyj.)
<?php if ($this->aiUsageTotals['providers']['grok']['cost_usd'] > 0) : ?> · <?php echo number_format((float) $this->aiUsageTotals['providers']['grok']['cost_usd'], 8, ',', ' '); ?> USD<?php endif; ?>
</span>
<span class="d-block"><strong>Łącznie:</strong> <?php echo number_format((int) $this->aiUsageTotals['total_tokens'], 0, ',', ' '); ?> tokenów</span>
</div>
<table class="table table-striped"> <table class="table table-striped">
<thead><tr> <thead><tr>
<th><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_RESULT_PARTICIPANT'); ?></th> <th><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_RESULT_PARTICIPANT'); ?></th>
@@ -41,6 +59,7 @@ use Joomla\CMS\Router\Route;
<th><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_RESULT_GRADE'); ?></th> <th><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_RESULT_GRADE'); ?></th>
<th><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_RESULT_EXECUTION_TIME'); ?></th> <th><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_RESULT_EXECUTION_TIME'); ?></th>
<th><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_RESULT_DATE'); ?></th> <th><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_RESULT_DATE'); ?></th>
<th><?php echo \Joomla\CMS\Language\Text::_('COM_TESTQUESTIONS_RESULT_AI_TOKENS'); ?></th>
</tr></thead> </tr></thead>
<tbody> <tbody>
<?php foreach ($this->items as $item) : ?> <?php foreach ($this->items as $item) : ?>
@@ -58,6 +77,20 @@ use Joomla\CMS\Router\Route;
<td><?php echo $this->escape($grade); ?></td> <td><?php echo $this->escape($grade); ?></td>
<td><?php echo floor((int) $item->execution_time / 60); ?>:<?php echo str_pad((string) ((int) $item->execution_time % 60), 2, '0', STR_PAD_LEFT); ?></td> <td><?php echo floor((int) $item->execution_time / 60); ?>:<?php echo str_pad((string) ((int) $item->execution_time % 60), 2, '0', STR_PAD_LEFT); ?></td>
<td><?php echo HTMLHelper::_('date', $item->created, 'Y-m-d H:i'); ?></td> <td><?php echo HTMLHelper::_('date', $item->created, 'Y-m-d H:i'); ?></td>
<td>
<?php if ($item->ai_total_tokens > 0) : ?>
<small class="d-block text-muted">
OpenAI: <?php echo number_format((int) $item->ai_tokens_by_provider['openai'], 0, ',', ' '); ?> ·
Grok: <?php echo number_format((int) $item->ai_tokens_by_provider['grok'], 0, ',', ' '); ?>
</small>
<small class="d-block text-muted">
<?php echo number_format((int) $item->ai_total_tokens, 0, ',', ' '); ?> łącznie
<?php if ($item->ai_cost_usd > 0) : ?> · <?php echo number_format((float) $item->ai_cost_usd, 8, ',', ' '); ?> USD<?php endif; ?>
</small>
<?php else : ?>
—
<?php endif; ?>
</td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>
+1 -1
View File
@@ -4,7 +4,7 @@
<creationDate>2026-10-14</creationDate> <creationDate>2026-10-14</creationDate>
<author>Komponent</author> <author>Komponent</author>
<license>GNU General Public License version 2 or later</license> <license>GNU General Public License version 2 or later</license>
<version>1.43.7</version> <version>1.43.11</version>
<description>COM_TESTQUESTIONS_DESCRIPTION</description> <description>COM_TESTQUESTIONS_DESCRIPTION</description>
<element>com_testquestions</element> <element>com_testquestions</element>
<namespace path="src">Komponent\Component\Testquestions</namespace> <namespace path="src">Komponent\Component\Testquestions</namespace>