Display OpenRouter credit balance in UI (#5513)

* Display OpenRouter credit balance in UI

Adds a "View Remaining Credits" click handler that fetches the
current balance from the OpenRouter /credits endpoint via a new
server-side /api/openrouter/credits route, and renders it next
to the link. The anchor still points at openrouter.ai/account
so middle-click / right-click "open in new tab" keeps working.

* Return 500 on OpenRouter credits failure

* Reduce to two decimals

* Update view credits URL

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
This commit is contained in:
Stagnating
2026-04-23 23:21:28 +03:00
committed by GitHub
parent 752ae243b9
commit a028bec87b
3 changed files with 62 additions and 2 deletions
+4 -2
View File
@@ -2446,7 +2446,8 @@
<small data-i18n="Click Authorize below or get the key from"> <small data-i18n="Click Authorize below or get the key from">
Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>. Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>.
<br> <br>
<a href="https://openrouter.ai/account" target="_blank" data-i18n="View Remaining Credits">View Remaining Credits</a> <a href="https://openrouter.ai/settings/credits" target="_blank" class="openrouter_view_credits" data-i18n="View Remaining Credits">View Remaining Credits</a>
<span class="openrouter_credits_display marginLeft5"></span>
</div> </div>
<div class="flex-container"> <div class="flex-container">
<input id="api_key_openrouter-tg" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off"> <input id="api_key_openrouter-tg" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off">
@@ -3179,7 +3180,8 @@
<small data-i18n="Click Authorize below or get the key from"> <small data-i18n="Click Authorize below or get the key from">
Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>. Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>.
<br> <br>
<a href="https://openrouter.ai/account" target="_blank" data-i18n="View Remaining Credits">View Remaining Credits</a> <a href="https://openrouter.ai/settings/credits" target="_blank" class="openrouter_view_credits" data-i18n="View Remaining Credits">View Remaining Credits</a>
<span class="openrouter_credits_display marginLeft5"></span>
</div> </div>
<div class="flex-container"> <div class="flex-container">
<input id="api_key_openrouter" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off"> <input id="api_key_openrouter" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off">
+23
View File
@@ -1120,5 +1120,28 @@ export async function initSecrets() {
warningElement.toggle(value.length > 0); warningElement.toggle(value.length > 0);
}); });
$('.openrouter_authorize').on('click', authorizeOpenRouter); $('.openrouter_authorize').on('click', authorizeOpenRouter);
$(document).on('click', '.openrouter_view_credits', async function (event) {
event.preventDefault();
const display = $(this).siblings('.openrouter_credits_display').first();
display.text(t`Loading…`);
try {
const response = await fetch('/api/openrouter/credits', {
method: 'POST',
headers: getRequestHeaders(),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
if (typeof data.remaining !== 'number') {
throw new Error('Invalid response');
}
display.text(`$${data.remaining.toFixed(2)}`);
} catch (error) {
console.error('Failed to fetch OpenRouter credits:', error);
display.text('');
toastr.error(t`Could not fetch OpenRouter credits. Please try again.`);
}
});
registerSecretSlashCommands(); registerSecretSlashCommands();
} }
+35
View File
@@ -100,6 +100,41 @@ router.post('/models/image', async (_req, res) => {
} }
}); });
router.post('/credits', async (req, res) => {
try {
const key = readSecret(req.user.directories, SECRET_KEYS.OPENROUTER);
if (!key) {
console.warn('OpenRouter API key not found');
return res.sendStatus(400);
}
const response = await fetch(`${API_OPENROUTER}/credits`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${key}`,
},
});
if (!response.ok) {
console.warn('OpenRouter credits request failed', response.statusText);
return res.sendStatus(500);
}
/** @type {any} */
const data = await response.json();
const totalCredits = data.data?.total_credits ?? 0;
const totalUsage = data.data?.total_usage ?? 0;
const remaining = totalCredits - totalUsage;
return res.json({ remaining, total_credits: totalCredits, total_usage: totalUsage });
} catch (error) {
console.error(error);
return res.sendStatus(500);
}
});
router.post('/image/generate', async (req, res) => { router.post('/image/generate', async (req, res) => {
try { try {
const key = readSecret(req.user.directories, SECRET_KEYS.OPENROUTER); const key = readSecret(req.user.directories, SECRET_KEYS.OPENROUTER);