Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
A
aisbf
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nexlab
aisbf
Commits
b4d36755
Commit
b4d36755
authored
Apr 22, 2026
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
0.99.49
parent
f65694ef
Changes
9
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
122 additions
and
303 deletions
+122
-303
CHANGELOG.md
CHANGELOG.md
+2
-2
PYPI.md
PYPI.md
+3
-3
__init__.py
aisbf/__init__.py
+1
-1
main.py
main.py
+12
-1
pyproject.toml
pyproject.toml
+1
-1
setup.py
setup.py
+1
-2
add_payment_method.html
templates/dashboard/add_payment_method.html
+0
-276
billing.html
templates/dashboard/billing.html
+85
-6
wallet.html
templates/dashboard/wallet.html
+17
-11
No files found.
CHANGELOG.md
View file @
b4d36755
...
...
@@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.99.4
8
] - 2026-04-21
## [0.99.4
9
] - 2026-04-21
### Added
-
**Unified Wallet System**
...
...
@@ -29,7 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
-
Clarified token access patterns (global vs user tokens)
### Changed
-
Updated all version numbers to 0.99.4
8
-
Updated all version numbers to 0.99.4
9
-
Added wallet package to pyproject.toml
## [0.99.26] - 2026-04-16
...
...
PYPI.md
View file @
b4d36755
...
...
@@ -41,8 +41,8 @@ python -m build
```
This creates:
- `dist/aisbf-0.99.4
8
.tar.gz` - Source distribution
- `dist/aisbf-0.99.4
8
-py3-none-any.whl` - Wheel distribution
- `dist/aisbf-0.99.4
9
.tar.gz` - Source distribution
- `dist/aisbf-0.99.4
9
-py3-none-any.whl` - Wheel distribution
## Testing the Package
...
...
@@ -50,7 +50,7 @@ This creates:
```
bash
# Install from the built wheel
pip install dist/aisbf-0.99.4
8
-py3-none-any.whl
pip install dist/aisbf-0.99.4
9
-py3-none-any.whl
# Test the installation
aisbf status
...
...
aisbf/__init__.py
View file @
b4d36755
...
...
@@ -54,7 +54,7 @@ from .auth.qwen import QwenOAuth2
from
.handlers
import
RequestHandler
,
RotationHandler
,
AutoselectHandler
from
.utils
import
count_messages_tokens
,
split_messages_into_chunks
,
get_max_request_tokens_for_model
__version__
=
"0.99.4
8
"
__version__
=
"0.99.4
9
"
__all__
=
[
# Config
"config"
,
...
...
main.py
View file @
b4d36755
...
...
@@ -8171,6 +8171,10 @@ async def dashboard_wallet(request: Request):
all_gateways
=
db
.
get_payment_gateway_settings
()
enabled_gateways
=
{
k
:
v
for
k
,
v
in
all_gateways
.
items
()
if
v
.
get
(
'enabled'
,
False
)}
# Get user's saved Stripe credit cards for auto top-up
stripe_cards
=
[
m
for
m
in
db
.
get_user_payment_methods
(
user_id
)
if
m
.
get
(
'type'
)
==
'stripe'
or
m
.
get
(
'gateway'
)
==
'stripe'
]
return
templates
.
TemplateResponse
(
request
=
request
,
name
=
"dashboard/wallet.html"
,
...
...
@@ -8179,6 +8183,7 @@ async def dashboard_wallet(request: Request):
"wallet"
:
wallet
,
"enabled_gateways"
:
enabled_gateways
,
"currency_symbol"
:
db
.
get_currency_settings
()
.
get
(
'currency_symbol'
,
'$'
),
"stripe_cards"
:
stripe_cards
,
}
)
except
ImportError
:
...
...
@@ -8348,6 +8353,11 @@ async def dashboard_billing(request: Request):
except
Exception
:
wallet
=
{
'balance'
:
'0.00'
,
'currency_code'
:
currency_code
,
'auto_topup_enabled'
:
False
}
# Get Stripe publishable key
stripe_publishable_key
=
""
if
'stripe'
in
gateways
and
gateways
[
'stripe'
]
.
get
(
'enabled'
):
stripe_publishable_key
=
gateways
[
'stripe'
]
.
get
(
'publishable_key'
,
''
)
return
templates
.
TemplateResponse
(
request
=
request
,
name
=
"dashboard/billing.html"
,
...
...
@@ -8358,7 +8368,8 @@ async def dashboard_billing(request: Request):
"transactions"
:
transactions
,
"enabled_gateways"
:
enabled_gateways
,
"wallet"
:
wallet
,
"currency_symbol"
:
currency_settings
.
get
(
'currency_symbol'
,
'$'
)
"currency_symbol"
:
currency_settings
.
get
(
'currency_symbol'
,
'$'
),
"stripe_publishable_key"
:
stripe_publishable_key
,
}
)
...
...
pyproject.toml
View file @
b4d36755
...
...
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name
=
"aisbf"
version
=
"0.99.4
8
"
version
=
"0.99.4
9
"
description
=
"AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations"
readme
=
"README.md"
license
=
"GPL-3.0-or-later"
...
...
setup.py
View file @
b4d36755
...
...
@@ -49,7 +49,7 @@ class InstallCommand(_install):
setup
(
name
=
"aisbf"
,
version
=
"0.99.4
8
"
,
version
=
"0.99.4
9
"
,
author
=
"AISBF Contributors"
,
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"
,
...
...
@@ -227,7 +227,6 @@ setup(
'templates/dashboard/pricing.html'
,
'templates/dashboard/subscription.html'
,
'templates/dashboard/billing.html'
,
'templates/dashboard/add_payment_method.html'
,
'templates/dashboard/paypal_connect.html'
,
'templates/dashboard/cache_settings.html'
,
'templates/dashboard/wallet.html'
,
...
...
templates/dashboard/add_payment_method.html
deleted
100644 → 0
View file @
f65694ef
This diff is collapsed.
Click to expand it.
templates/dashboard/billing.html
View file @
b4d36755
...
...
@@ -84,14 +84,19 @@
<div
style=
"text-align: center; padding: 40px; color: #a0a0a0;"
>
<i
class=
"fas fa-credit-card fa-4x mb-3"
></i>
<h4
style=
"margin-bottom: 15px;"
>
No payment methods configured
</h4>
<p
style=
"margin-bottom: 20px;"
>
Add a payment method to upgrade your plan and manage subscriptions
</p>
<a
href=
"{{ url_for(request, '/dashboard/billing/add-method') }}"
class=
"btn"
>
Add Payment Method
</a>
<p
style=
"margin-bottom: 20px;"
>
Add a credit card to enable automatic subscription renewals.
</p>
{% if 'stripe' in enabled_gateways %}
<button
id=
"stripe-button"
class=
"btn"
><i
class=
"fab fa-cc-stripe me-2"
></i>
Add Credit Card
</button>
{% endif %}
</div>
{% endif %}
<div
style=
"display: flex; gap: 10px; margin-top: 20px;"
>
<a
href=
"{{ url_for(request, '/dashboard/billing/add-method') }}"
class=
"btn"
>
<i
class=
"fas fa-plus me-2"
></i>
Add Payment Method
<div
style=
"display: flex; gap: 10px; margin-top: 20px; flex-wrap: wrap; align-items: center;"
>
{% if 'stripe' in enabled_gateways %}
<button
id=
"stripe-button-bottom"
class=
"btn"
><i
class=
"fab fa-cc-stripe me-2"
></i>
Add Credit Card
</button>
{% endif %}
<a
href=
"{{ url_for(request, '/dashboard/wallet') }}"
class=
"btn btn-secondary"
>
<i
class=
"fas fa-wallet me-2"
></i>
Top Up Wallet
</a>
</div>
</div>
...
...
@@ -295,9 +300,83 @@
}
</style>
<!-- Stripe Card Modal -->
<div
id=
"stripe-modal"
style=
"display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);z-index:1000;align-items:center;justify-content:center;"
>
<div
style=
"background:#16213e;border:2px solid #4a9eff;border-radius:8px;padding:30px;max-width:500px;width:90%;position:relative;"
>
<button
id=
"close-stripe-modal"
style=
"position:absolute;top:15px;right:15px;background:transparent;border:none;color:#e0e0e0;font-size:24px;cursor:pointer;"
>
×
</button>
<h3
style=
"margin:0 0 20px 0;color:#4a9eff;"
><i
class=
"fab fa-cc-stripe me-2"
></i>
Add Credit Card
</h3>
<form
id=
"stripe-card-form"
>
<div
style=
"margin-bottom:20px;"
>
<label
style=
"display:block;margin-bottom:8px;color:#e0e0e0;font-weight:500;"
>
Card Details
</label>
<div
id=
"card-element"
style=
"background:#1a1a2e;padding:12px;border:1px solid #0f3460;border-radius:4px;"
></div>
<div
id=
"card-errors"
style=
"color:#dc3545;margin-top:8px;font-size:14px;"
></div>
</div>
<div
style=
"display:flex;gap:10px;justify-content:flex-end;"
>
<button
type=
"button"
id=
"cancel-stripe"
class=
"btn btn-secondary"
>
Cancel
</button>
<button
type=
"submit"
id=
"stripe-submit"
class=
"btn"
><i
class=
"fas fa-plus me-2"
></i>
Add Card
</button>
</div>
</form>
</div>
</div>
<script
src=
"https://js.stripe.com/v3/"
></script>
<script>
document
.
addEventListener
(
'DOMContentLoaded'
,
function
()
{
console
.
log
(
'Billing page JavaScript loaded'
);
// existing delete/set-default logic unchanged below...
{
%
if
stripe_publishable_key
%
}
const
STRIPE_KEY
=
'{{ stripe_publishable_key }}'
;
let
stripe
,
cardElement
,
stripeReady
=
false
;
function
openStripeModal
()
{
document
.
getElementById
(
'stripe-modal'
).
style
.
display
=
'flex'
;
if
(
!
stripeReady
)
{
stripe
=
Stripe
(
STRIPE_KEY
);
cardElement
=
stripe
.
elements
().
create
(
'card'
,
{
style
:
{
base
:
{
color
:
'#e0e0e0'
,
fontSize
:
'16px'
,
'::placeholder'
:
{
color
:
'#a0a0a0'
}
},
invalid
:
{
color
:
'#dc3545'
}
}
});
cardElement
.
mount
(
'#card-element'
);
cardElement
.
on
(
'change'
,
e
=>
{
document
.
getElementById
(
'card-errors'
).
textContent
=
e
.
error
?
e
.
error
.
message
:
''
;
});
stripeReady
=
true
;
}
}
[
'stripe-button'
,
'stripe-button-bottom'
].
forEach
(
id
=>
{
const
btn
=
document
.
getElementById
(
id
);
if
(
btn
)
btn
.
addEventListener
(
'click'
,
openStripeModal
);
});
document
.
getElementById
(
'close-stripe-modal'
).
addEventListener
(
'click'
,
()
=>
document
.
getElementById
(
'stripe-modal'
).
style
.
display
=
'none'
);
document
.
getElementById
(
'cancel-stripe'
).
addEventListener
(
'click'
,
()
=>
document
.
getElementById
(
'stripe-modal'
).
style
.
display
=
'none'
);
document
.
getElementById
(
'stripe-modal'
).
addEventListener
(
'click'
,
e
=>
{
if
(
e
.
target
===
document
.
getElementById
(
'stripe-modal'
))
document
.
getElementById
(
'stripe-modal'
).
style
.
display
=
'none'
;
});
document
.
getElementById
(
'stripe-card-form'
).
addEventListener
(
'submit'
,
async
e
=>
{
e
.
preventDefault
();
const
btn
=
document
.
getElementById
(
'stripe-submit'
);
btn
.
disabled
=
true
;
btn
.
innerHTML
=
'<i class="fas fa-spinner fa-spin me-2"></i>Processing…'
;
const
{
error
,
paymentMethod
}
=
await
stripe
.
createPaymentMethod
({
type
:
'card'
,
card
:
cardElement
});
if
(
error
)
{
document
.
getElementById
(
'card-errors'
).
textContent
=
error
.
message
;
btn
.
disabled
=
false
;
btn
.
innerHTML
=
'<i class="fas fa-plus me-2"></i>Add Card'
;
return
;
}
const
res
=
await
fetch
(
'/dashboard/billing/add-method/stripe'
,
{
method
:
'POST'
,
headers
:
{
'Content-Type'
:
'application/json'
},
body
:
JSON
.
stringify
({
payment_method_id
:
paymentMethod
.
id
})
});
const
data
=
await
res
.
json
();
if
(
data
.
success
)
{
window
.
location
.
href
=
'/dashboard/billing?success='
+
encodeURIComponent
(
data
.
message
||
'Card added'
);
}
else
{
document
.
getElementById
(
'card-errors'
).
textContent
=
data
.
error
||
'Failed to add card'
;
btn
.
disabled
=
false
;
btn
.
innerHTML
=
'<i class="fas fa-plus me-2"></i>Add Card'
;
}
});
{
%
endif
%
}
// Delete payment method
const
deleteButtons
=
document
.
querySelectorAll
(
'.delete-method-btn'
);
...
...
templates/dashboard/wallet.html
View file @
b4d36755
...
...
@@ -130,6 +130,7 @@
</div>
<div
id=
"auto-topup-settings"
{%
if
not
wallet
.
auto_topup_enabled
%}
style=
"display:none;"
{%
endif
%}
>
{% if stripe_cards %}
<div
style=
"margin-bottom: 14px;"
>
<label
style=
"display: block; color: #a0a0a0; font-size: 13px; margin-bottom: 6px;"
>
Top-up amount
</label>
<div
style=
"position: relative;"
>
...
...
@@ -148,21 +149,26 @@
value=
"{{ wallet.auto_topup_threshold or '' }}"
step=
"0.01"
min=
"1"
>
</div>
</div>
<div
style=
"margin-bottom: 18px;"
>
<label
style=
"display: block; color: #a0a0a0; font-size: 13px; margin-bottom: 6px;"
>
Payment method
</label>
<select
id=
"auto-topup-payment-method"
style=
"width: 100%; padding: 10px; background: #1a1a2e; border: 1px solid #0f3460; border-radius: 6px; color: #e0e0e0; font-size: 14px;"
>
{% for name in enabled_gateways.keys() | select('in', ['stripe', 'paypal']) %}
<option
value=
"{{ name }}"
{%
if
wallet
.
auto_topup_payment_method =
=
name
%}
selected
{%
endif
%}
>
{{ name | capitalize }}
</option>
{% endfor %}
</select>
<div
style=
"margin-bottom: 14px; color: #a0a0a0; font-size: 13px;"
>
<i
class=
"fab fa-cc-stripe me-1"
></i>
Charged to your default credit card:
{% set default_card = stripe_cards | selectattr('is_default') | first | default(stripe_cards[0]) %}
<strong
style=
"color: #e0e0e0;"
>
•••• {{ default_card.last4 or '????' }} ({{ default_card.brand | default('Card') | capitalize }})
</strong>
</div>
<button
id=
"save-auto-topup"
style=
"background: #17a2b8; border: none; color: white; padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; transition: background .15s;"
>
<i
class=
"fas fa-save me-2"
></i>
Save Settings
</button>
{% else %}
<div
style=
"background: #1a1a2e; border: 1px solid #0f3460; border-radius: 6px; padding: 16px; text-align: center;"
>
<p
style=
"color: #a0a0a0; margin: 0 0 12px 0; font-size: 13px;"
>
Auto top-up charges your saved credit card automatically.
<br>
You don't have a credit card on file yet.
</p>
<a
href=
"{{ url_for(request, '/dashboard/billing') }}"
class=
"btn"
style=
"font-size: 13px; padding: 8px 16px;"
>
<i
class=
"fab fa-cc-stripe me-2"
></i>
Add Credit Card
</a>
</div>
{% endif %}
</div>
{% if not wallet.auto_topup_enabled %}
...
...
@@ -342,7 +348,7 @@ document.addEventListener('DOMContentLoaded', function () {
enabled
:
toggle
.
checked
,
topup_amount
:
parseFloat
(
document
.
getElementById
(
'auto-topup-amount'
).
value
)
||
null
,
threshold_amount
:
parseFloat
(
document
.
getElementById
(
'auto-topup-threshold'
).
value
)
||
null
,
payment_method
:
document
.
getElementById
(
'auto-topup-payment-method'
).
value
||
null
payment_method
:
'stripe'
};
fetch
(
'/api/wallet/auto-topup'
,
{
method
:
'POST'
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment