Bump version to 0.99.56; fix pricing page and add usage/quota page

- Fix ReferenceError: url_for is not defined in pricing JS (was calling
  server-side Jinja2 helper from client-side JavaScript)
- Add new POST /dashboard/subscribe/{tier_id} endpoint with smart payment
  logic: deducts from wallet if sufficient, otherwise charges saved Stripe
  card for the exact plan amount; returns clear error when neither is
  available
- Add POST /dashboard/subscribe/free for downgrade flow
- Fix plan description field names (max_requests_day → max_requests_per_day,
  etc.) across pricing.html and subscription.html; numbers now formatted
  with thousands separators
- Fix pricing card layout so a single plan is centered and capped in width
  instead of stretching full-screen
- Add is_default and is_active to get_user_tier() return dict
- Add upgrade CTA banner in dashboard overview subscription section when
  higher plans are available
- Add subscription hint banner in wallet page
- Add new Usage & Quotas page (/dashboard/usage) with progress bars for
  daily/monthly requests, providers, rotations, autoselections, and tokens;
  bars warn at 75% and turn red at 90%
- Add Usage link to main nav and account dropdown
- Register usage.html in setup.py data_files
parent 612a8358
...@@ -54,7 +54,7 @@ from .auth.qwen import QwenOAuth2 ...@@ -54,7 +54,7 @@ from .auth.qwen import QwenOAuth2
from .handlers import RequestHandler, RotationHandler, AutoselectHandler from .handlers import RequestHandler, RotationHandler, AutoselectHandler
from .utils import count_messages_tokens, split_messages_into_chunks, get_max_request_tokens_for_model from .utils import count_messages_tokens, split_messages_into_chunks, get_max_request_tokens_for_model
__version__ = "0.99.54" __version__ = "0.99.56"
__all__ = [ __all__ = [
# Config # Config
"config", "config",
......
...@@ -2637,12 +2637,12 @@ class DatabaseManager: ...@@ -2637,12 +2637,12 @@ class DatabaseManager:
SELECT t.id, t.name, t.description, t.price_monthly, t.price_yearly, SELECT t.id, t.name, t.description, t.price_monthly, t.price_yearly,
t.max_requests_per_day, t.max_requests_per_month, t.max_providers, t.max_requests_per_day, t.max_requests_per_month, t.max_providers,
t.max_rotations, t.max_autoselections, t.max_rotation_models, t.max_rotations, t.max_autoselections, t.max_rotation_models,
t.max_autoselection_models t.max_autoselection_models, t.is_default, t.is_active
FROM users u FROM users u
JOIN account_tiers t ON u.tier_id = t.id JOIN account_tiers t ON u.tier_id = t.id
WHERE u.id = {placeholder} WHERE u.id = {placeholder}
''', (user_id,)) ''', (user_id,))
row = cursor.fetchone() row = cursor.fetchone()
if row: if row:
return { return {
...@@ -2657,7 +2657,9 @@ class DatabaseManager: ...@@ -2657,7 +2657,9 @@ class DatabaseManager:
'max_rotations': row[8], 'max_rotations': row[8],
'max_autoselections': row[9], 'max_autoselections': row[9],
'max_rotation_models': row[10], 'max_rotation_models': row[10],
'max_autoselection_models': row[11] 'max_autoselection_models': row[11],
'is_default': bool(row[12]),
'is_active': bool(row[13]),
} }
return None return None
......
This diff is collapsed.
...@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" ...@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "aisbf" name = "aisbf"
version = "0.99.54" version = "0.99.56"
description = "AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations" description = "AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations"
readme = "README.md" readme = "README.md"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
......
...@@ -49,7 +49,7 @@ class InstallCommand(_install): ...@@ -49,7 +49,7 @@ class InstallCommand(_install):
setup( setup(
name="aisbf", name="aisbf",
version="0.99.54", version="0.99.56",
author="AISBF Contributors", author="AISBF Contributors",
author_email="stefy@nexlab.net", author_email="stefy@nexlab.net",
description="AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations", description="AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations",
...@@ -230,6 +230,7 @@ setup( ...@@ -230,6 +230,7 @@ setup(
'templates/dashboard/paypal_connect.html', 'templates/dashboard/paypal_connect.html',
'templates/dashboard/cache_settings.html', 'templates/dashboard/cache_settings.html',
'templates/dashboard/wallet.html', 'templates/dashboard/wallet.html',
'templates/dashboard/usage.html',
'templates/dashboard/error.html', 'templates/dashboard/error.html',
]), ]),
# Install static files (extension and favicon) # Install static files (extension and favicon)
......
...@@ -552,6 +552,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. ...@@ -552,6 +552,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
{% if request.session.user_id %} {% if request.session.user_id %}
<a href="{{ url_for(request, '/dashboard/user/tokens') }}" {% if '/user/tokens' in request.path %}class="active"{% endif %}>API Tokens</a> <a href="{{ url_for(request, '/dashboard/user/tokens') }}" {% if '/user/tokens' in request.path %}class="active"{% endif %}>API Tokens</a>
<a href="{{ url_for(request, '/dashboard/wallet') }}" {% if '/wallet' in request.path %}class="active"{% endif %}>Wallet</a> <a href="{{ url_for(request, '/dashboard/wallet') }}" {% if '/wallet' in request.path %}class="active"{% endif %}>Wallet</a>
<a href="{{ url_for(request, '/dashboard/usage') }}" {% if '/usage' in request.path %}class="active"{% endif %}>Usage</a>
{% endif %} {% endif %}
{% if request.session.role == 'admin' %} {% if request.session.role == 'admin' %}
<a href="{{ url_for(request, '/dashboard/users') }}" {% if '/users' in request.path %}class="active"{% endif %}>Users</a> <a href="{{ url_for(request, '/dashboard/users') }}" {% if '/users' in request.path %}class="active"{% endif %}>Users</a>
...@@ -577,6 +578,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. ...@@ -577,6 +578,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<a href="{{ url_for(request, '/dashboard/subscription') }}">Subscription</a> <a href="{{ url_for(request, '/dashboard/subscription') }}">Subscription</a>
<a href="{{ url_for(request, '/dashboard/wallet') }}">Wallet</a> <a href="{{ url_for(request, '/dashboard/wallet') }}">Wallet</a>
<a href="{{ url_for(request, '/dashboard/billing') }}">Billing</a> <a href="{{ url_for(request, '/dashboard/billing') }}">Billing</a>
<a href="{{ url_for(request, '/dashboard/usage') }}">Usage &amp; Quotas</a>
<a href="{{ url_for(request, '/dashboard/change-password') }}">Change Password</a> <a href="{{ url_for(request, '/dashboard/change-password') }}">Change Password</a>
{% endif %} {% endif %}
{% endif %} {% endif %}
......
This diff is collapsed.
...@@ -47,21 +47,21 @@ ...@@ -47,21 +47,21 @@
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 20px;"> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 20px;">
<div style="text-align: center; padding: 15px; background: #0f2840; border-radius: 5px;"> <div style="text-align: center; padding: 15px; background: #0f2840; border-radius: 5px;">
<div style="font-size: 24px; font-weight: bold; color: #4a9eff; margin-bottom: 5px;"> <div style="font-size: 24px; font-weight: bold; color: #4a9eff; margin-bottom: 5px;">
{% if current_tier.max_requests_day == -1 %} {% if current_tier.max_requests_per_day == -1 %}
{% else %} {% elif current_tier.max_requests_per_day is not none %}
{{ current_tier.max_requests_day }} {{ "{:,}".format(current_tier.max_requests_per_day) }}
{% endif %} {% else %}∞{% endif %}
</div> </div>
<div style="color: #a0a0a0; font-size: 12px;">Requests per day</div> <div style="color: #a0a0a0; font-size: 12px;">Requests per day</div>
</div> </div>
<div style="text-align: center; padding: 15px; background: #0f2840; border-radius: 5px;"> <div style="text-align: center; padding: 15px; background: #0f2840; border-radius: 5px;">
<div style="font-size: 24px; font-weight: bold; color: #4ade80; margin-bottom: 5px;"> <div style="font-size: 24px; font-weight: bold; color: #4ade80; margin-bottom: 5px;">
{% if current_tier.max_requests_month == -1 %} {% if current_tier.max_requests_per_month == -1 %}
{% else %} {% elif current_tier.max_requests_per_month is not none %}
{{ current_tier.max_requests_month }} {{ "{:,}".format(current_tier.max_requests_per_month) }}
{% endif %} {% else %}∞{% endif %}
</div> </div>
<div style="color: #a0a0a0; font-size: 12px;">Requests per month</div> <div style="color: #a0a0a0; font-size: 12px;">Requests per month</div>
</div> </div>
......
This diff is collapsed.
...@@ -372,6 +372,11 @@ ...@@ -372,6 +372,11 @@
<span>Wallet</span> <span>Wallet</span>
<i class="fas fa-chevron-right arrow"></i> <i class="fas fa-chevron-right arrow"></i>
</a> </a>
<a href="{{ url_for(request, '/dashboard/usage') }}" class="action-card">
<i class="fas fa-gauge-high"></i>
<span>Usage</span>
<i class="fas fa-chevron-right arrow"></i>
</a>
</div> </div>
</div> </div>
</div> </div>
...@@ -392,15 +397,17 @@ ...@@ -392,15 +397,17 @@
{% if current_tier.is_default %} {% if current_tier.is_default %}
<span class="badge badge-free">Free Tier</span> <span class="badge badge-free">Free Tier</span>
{% else %} {% else %}
<div class="sub-price">{{ currency_symbol }}{{ current_tier.price_monthly }}<span style="font-size:.9rem;color:#888;">/mo</span></div> <div class="sub-price">{{ currency_symbol }}{{ "%.2f"|format(current_tier.price_monthly) }}<span style="font-size:.9rem;color:#888;">/mo</span></div>
<div class="sub-price-sub">or {{ currency_symbol }}{{ current_tier.price_yearly }}/year</div> {% if current_tier.price_yearly %}
<div class="sub-price-sub">or {{ currency_symbol }}{{ "%.2f"|format(current_tier.price_yearly) }}/year</div>
{% endif %}
{% endif %} {% endif %}
</div> </div>
{% if subscription %} {% if subscription %}
<div style="text-align:right;"> <div style="text-align:right;">
<span class="badge badge-{{ subscription.status }}">{{ subscription.status|title }}</span> <span class="badge badge-{{ subscription.status }}">{{ subscription.status|title }}</span>
{% if subscription.expires_at %} {% if subscription.next_billing_date %}
<div class="renew-label">Renews {{ subscription.expires_at }}</div> <div class="renew-label">Renews {{ subscription.next_billing_date }}</div>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
...@@ -408,6 +415,29 @@ ...@@ -408,6 +415,29 @@
{% if not payment_methods or payment_methods|length == 0 %} {% if not payment_methods or payment_methods|length == 0 %}
<a href="{{ url_for(request, '/dashboard/billing/add-method') }}" class="btn btn-primary" style="margin-top:1rem; margin-left:0; display:inline-block;">Add Payment Method</a> <a href="{{ url_for(request, '/dashboard/billing/add-method') }}" class="btn btn-primary" style="margin-top:1rem; margin-left:0; display:inline-block;">Add Payment Method</a>
{% endif %} {% endif %}
<!-- Upgrade CTA -->
{% if upgrade_tiers %}
<div style="margin-top:1rem; padding:1rem; background:linear-gradient(135deg,rgba(74,158,255,.12),rgba(103,126,234,.08)); border:1px solid rgba(74,158,255,.4); border-radius:8px;">
<div style="display:flex; align-items:center; justify-content:space-between; gap:.75rem; flex-wrap:wrap;">
<div>
<div style="font-size:.9rem; font-weight:600; color:#e0e0e0; margin-bottom:.25rem;">
<i class="fas fa-rocket" style="color:#4a9eff;"></i> Unlock more power
</div>
<div style="font-size:.8rem; color:#a0a0a0;">
{% if upgrade_tiers|length == 1 %}
Upgrade to <strong style="color:#4a9eff;">{{ upgrade_tiers[0].name }}</strong> for {{ currency_symbol }}{{ "%.2f"|format(upgrade_tiers[0].price_monthly) }}/mo
{% else %}
{{ upgrade_tiers|length }} higher plans available — more requests, more providers
{% endif %}
</div>
</div>
<a href="{{ url_for(request, '/dashboard/pricing') }}" style="background:linear-gradient(135deg,#4a9eff,#667eea); color:white; text-decoration:none; padding:.5rem 1rem; border-radius:6px; font-size:.85rem; font-weight:600; white-space:nowrap; flex-shrink:0;">
<i class="fas fa-arrow-up me-1"></i> Upgrade Plan
</a>
</div>
</div>
{% endif %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
......
...@@ -5,6 +5,20 @@ ...@@ -5,6 +5,20 @@
{% block content %} {% block content %}
<h2 style="margin-bottom: 20px;"><i class="fas fa-wallet me-2"></i>Wallet</h2> <h2 style="margin-bottom: 20px;"><i class="fas fa-wallet me-2"></i>Wallet</h2>
{% if upgrade_tiers %}
<!-- Subscription upgrade hint -->
<div style="background: linear-gradient(135deg, rgba(74,158,255,.12), rgba(103,126,234,.08)); border: 1px solid rgba(74,158,255,.5); border-radius: 8px; padding: 14px 20px; margin-bottom: 20px; display: flex; align-items: center; justify-content: space-between; gap: 15px; flex-wrap: wrap;">
<div>
<i class="fas fa-crown" style="color: #ffc107; margin-right: 8px;"></i>
<span style="color: #e0e0e0; font-weight: 600;">Did you know?</span>
<span style="color: #a0a0a0; margin-left: 6px;">You can use your wallet balance to subscribe to a paid plan and unlock higher limits.</span>
</div>
<a href="{{ url_for(request, '/dashboard/pricing') }}" style="background: linear-gradient(135deg, #4a9eff, #667eea); color: white; text-decoration: none; padding: 8px 16px; border-radius: 6px; font-size: 14px; font-weight: 600; white-space: nowrap; flex-shrink: 0;">
<i class="fas fa-arrow-up me-1"></i> View Plans
</a>
</div>
{% endif %}
<!-- Balance Banner --> <!-- Balance Banner -->
<div style="background: linear-gradient(135deg, #1a4a2e, #0f3460); border: 2px solid #28a745; border-radius: 8px; padding: 24px; margin-bottom: 20px;"> <div style="background: linear-gradient(135deg, #1a4a2e, #0f3460); border: 2px solid #28a745; border-radius: 8px; padding: 24px; margin-bottom: 20px;">
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 15px;"> <div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 15px;">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment