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>

a028bec87b53f617ff7aa3652ad0473bcb0d1529

Stagnating <stagnantarchives@gmail.com>

Signed
3 files changed, +62 -2Ignore whitespace
public/index.html+4 -2
@@ -2446,7 +2446,8 @@
24462446 <small data-i18n="Click Authorize below or get the key from">
24472447 Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>.
24482448 <br>
24492449 <a href="https://openrouter.ai/accountsettings/credits" target="_blank" class="openrouter_view_credits" data-i18n="View Remaining Credits">View Remaining Credits</a>
2450+ <span class="openrouter_credits_display marginLeft5"></span>
24502451 </div>
24512452 <div class="flex-container">
24522453 <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 @@
31793180 <small data-i18n="Click Authorize below or get the key from">
31803181 Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>.
31813182 <br>
31823183 <a href="https://openrouter.ai/accountsettings/credits" target="_blank" class="openrouter_view_credits" data-i18n="View Remaining Credits">View Remaining Credits</a>
3184+ <span class="openrouter_credits_display marginLeft5"></span>
31833185 </div>
31843186 <div class="flex-container">
31853187 <input id="api_key_openrouter" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off">
public/scripts/secrets.js+23 -0
@@ -1120,5 +1120,28 @@ export async function initSecrets() {
11201120 warningElement.toggle(value.length > 0);
11211121 });
11221122 $('.openrouter_authorize').on('click', authorizeOpenRouter);
1123+ $(document).on('click', '.openrouter_view_credits', async function (event) {
1124+ event.preventDefault();
1125+ const display = $(this).siblings('.openrouter_credits_display').first();
1126+ display.text(t`Loading…`);
1127+ try {
1128+ const response = await fetch('/api/openrouter/credits', {
1129+ method: 'POST',
1130+ headers: getRequestHeaders(),
1131+ });
1132+ if (!response.ok) {
1133+ throw new Error(`HTTP ${response.status}`);
1134+ }
1135+ const data = await response.json();
1136+ if (typeof data.remaining !== 'number') {
1137+ throw new Error('Invalid response');
1138+ }
1139+ display.text(`$${data.remaining.toFixed(2)}`);
1140+ } catch (error) {
1141+ console.error('Failed to fetch OpenRouter credits:', error);
1142+ display.text('');
1143+ toastr.error(t`Could not fetch OpenRouter credits. Please try again.`);
1144+ }
1145+ });
11231146 registerSecretSlashCommands();
11241147}
src/endpoints/openrouter.js+35 -0
@@ -100,6 +100,41 @@ router.post('/models/image', async (_req, res) => {
100100 }
101101});
102102
103+router.post('/credits', async (req, res) => {
104+ try {
105+ const key = readSecret(req.user.directories, SECRET_KEYS.OPENROUTER);
106+
107+ if (!key) {
108+ console.warn('OpenRouter API key not found');
109+ return res.sendStatus(400);
110+ }
111+
112+ const response = await fetch(`${API_OPENROUTER}/credits`, {
113+ method: 'GET',
114+ headers: {
115+ 'Accept': 'application/json',
116+ 'Authorization': `Bearer ${key}`,
117+ },
118+ });
119+
120+ if (!response.ok) {
121+ console.warn('OpenRouter credits request failed', response.statusText);
122+ return res.sendStatus(500);
123+ }
124+
125+ /** @type {any} */
126+ const data = await response.json();
127+ const totalCredits = data.data?.total_credits ?? 0;
128+ const totalUsage = data.data?.total_usage ?? 0;
129+ const remaining = totalCredits - totalUsage;
130+
131+ return res.json({ remaining, total_credits: totalCredits, total_usage: totalUsage });
132+ } catch (error) {
133+ console.error(error);
134+ return res.sendStatus(500);
135+ }
136+});
137+
103138router.post('/image/generate', async (req, res) => {
104139 try {
105140 const key = readSecret(req.user.directories, SECRET_KEYS.OPENROUTER);