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:
+4
-2
@@ -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">
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user