feat(chat): Dwa klucze API - Free tier dla Flash, Paid dla Pro

- GOOGLE_GEMINI_API_KEY_FREE: klucz Free tier dla Flash (darmowy)
- GOOGLE_GEMINI_API_KEY: klucz Paid tier dla Pro (płatny)
- GeminiService automatycznie wybiera klucz na podstawie modelu
- Flash pricing ustawiony na $0.00 (Free tier)
- UI pokazuje Flash jako "Darmowy"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-01-29 11:19:59 +01:00
parent 26db9a7cc9
commit 1b2ba66ead
3 changed files with 78 additions and 9 deletions

46
app.py
View File

@ -4623,6 +4623,52 @@ def chat_send_message(conversation_id):
# Get model from request or session (flash = default, pro = premium) # Get model from request or session (flash = default, pro = premium)
model_choice = data.get('model') or session.get('chat_model', 'flash') model_choice = data.get('model') or session.get('chat_model', 'flash')
# Check Pro model limits (Flash is free - no limits)
if model_choice == 'pro':
# Users without limits (admins)
UNLIMITED_USERS = ['maciej.pienczyn@inpi.pl', 'artur.wiertel@waterm.pl']
if current_user.email not in UNLIMITED_USERS:
# Check daily and monthly limits for Pro
from database import AIApiCost
db_check = SessionLocal()
try:
# Daily limit: $2.00
today_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
daily_costs = db_check.query(AIApiCost).filter(
AIApiCost.user_id == current_user.id,
AIApiCost.timestamp >= today_start,
AIApiCost.model_name.like('%pro%')
).all()
daily_total = sum(c.total_cost_usd or 0 for c in daily_costs)
if daily_total >= 2.0:
return jsonify({
'success': False,
'error': 'Osiągnięto dzienny limit Pro ($2.00). Spróbuj jutro lub użyj darmowego modelu Flash.',
'limit_exceeded': 'daily',
'daily_used': round(daily_total, 2)
}), 429
# Monthly limit: $20.00
month_start = datetime.now().replace(day=1, hour=0, minute=0, second=0, microsecond=0)
monthly_costs = db_check.query(AIApiCost).filter(
AIApiCost.user_id == current_user.id,
AIApiCost.timestamp >= month_start,
AIApiCost.model_name.like('%pro%')
).all()
monthly_total = sum(c.total_cost_usd or 0 for c in monthly_costs)
if monthly_total >= 20.0:
return jsonify({
'success': False,
'error': 'Osiągnięto miesięczny limit Pro ($20.00). Użyj darmowego modelu Flash.',
'limit_exceeded': 'monthly',
'monthly_used': round(monthly_total, 2)
}), 429
finally:
db_check.close()
# Map model choice to actual model name # Map model choice to actual model name
model_map = { model_map = {
'flash': '3-flash', 'flash': '3-flash',

View File

@ -58,13 +58,14 @@ THINKING_LEVELS = {
} }
# Pricing per 1M tokens (USD) - updated 2026-01-29 # Pricing per 1M tokens (USD) - updated 2026-01-29
# Note: Flash on Free Tier = $0.00, Pro on Paid Tier = paid pricing
GEMINI_PRICING = { GEMINI_PRICING = {
'gemini-2.5-flash': {'input': 0.30, 'output': 2.50, 'thinking': 0}, 'gemini-2.5-flash': {'input': 0.30, 'output': 2.50, 'thinking': 0},
'gemini-2.5-flash-lite': {'input': 0.10, 'output': 0.40, 'thinking': 0}, 'gemini-2.5-flash-lite': {'input': 0.10, 'output': 0.40, 'thinking': 0},
'gemini-2.5-pro': {'input': 1.25, 'output': 10.00, 'thinking': 0}, 'gemini-2.5-pro': {'input': 1.25, 'output': 10.00, 'thinking': 0},
'gemini-2.0-flash': {'input': 0.10, 'output': 0.40, 'thinking': 0}, 'gemini-2.0-flash': {'input': 0.10, 'output': 0.40, 'thinking': 0},
'gemini-3-flash-preview': {'input': 0.50, 'output': 3.00, 'thinking': 1.00}, 'gemini-3-flash-preview': {'input': 0.00, 'output': 0.00, 'thinking': 0.00}, # Free tier!
'gemini-3-pro-preview': {'input': 2.00, 'output': 12.00, 'thinking': 4.00}, 'gemini-3-pro-preview': {'input': 2.00, 'output': 12.00, 'thinking': 4.00}, # Paid tier
} }
@ -82,12 +83,24 @@ class GeminiService:
Initialize Gemini service. Initialize Gemini service.
Args: Args:
api_key: Google AI API key (reads from GOOGLE_GEMINI_API_KEY env if not provided) api_key: Google AI API key (reads from env if not provided)
model: Model to use ('flash', 'flash-lite', 'pro', '3-flash', '3-pro') model: Model to use ('flash', 'flash-lite', 'pro', '3-flash', '3-pro')
thinking_level: Reasoning depth ('minimal', 'low', 'medium', 'high') thinking_level: Reasoning depth ('minimal', 'low', 'medium', 'high')
include_thoughts: Whether to include thinking process in response (for debugging) include_thoughts: Whether to include thinking process in response (for debugging)
API Keys (auto-selected by model):
- GOOGLE_GEMINI_API_KEY_FREE: Free tier for Flash models (no cost)
- GOOGLE_GEMINI_API_KEY: Paid tier for Pro models
""" """
self.api_key = api_key or os.getenv('GOOGLE_GEMINI_API_KEY') # Auto-select API key based on model (Free tier for Flash, Paid for Pro)
if api_key:
self.api_key = api_key
elif model in ('3-pro', 'pro'):
# Pro models use paid tier
self.api_key = os.getenv('GOOGLE_GEMINI_API_KEY')
else:
# Flash models prefer free tier, fallback to paid
self.api_key = os.getenv('GOOGLE_GEMINI_API_KEY_FREE') or os.getenv('GOOGLE_GEMINI_API_KEY')
# Debug: Log API key (masked) # Debug: Log API key (masked)
if self.api_key: if self.api_key:

View File

@ -733,6 +733,16 @@
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
} }
.thinking-option-badge.free {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
}
.thinking-option-price.free {
color: #10b981;
font-weight: 500;
font-style: normal;
}
.thinking-option-price { .thinking-option-price {
display: block; display: block;
font-size: 11px; font-size: 11px;
@ -1247,10 +1257,10 @@
<div class="thinking-option-header"> <div class="thinking-option-header">
<span class="thinking-option-icon"></span> <span class="thinking-option-icon"></span>
<span class="thinking-option-name">Gemini Flash</span> <span class="thinking-option-name">Gemini Flash</span>
<span class="thinking-option-badge">Domyślny</span> <span class="thinking-option-badge free">Darmowy</span>
</div> </div>
<p class="thinking-option-desc">Szybki i ekonomiczny. Dla prostych pytań o firmy, kontakty, wydarzenia.</p> <p class="thinking-option-desc">Szybki i skuteczny. Dla większości pytań o firmy, kontakty, wydarzenia.</p>
<span class="thinking-option-price">~$0.05/pytanie</span> <span class="thinking-option-price free">Bez opłat</span>
</div> </div>
<div class="thinking-option" data-model="pro" onclick="setModel('pro')"> <div class="thinking-option" data-model="pro" onclick="setModel('pro')">
<div class="thinking-option-header"> <div class="thinking-option-header">
@ -1258,8 +1268,8 @@
<span class="thinking-option-name">Gemini Pro</span> <span class="thinking-option-name">Gemini Pro</span>
<span class="thinking-option-badge premium">Premium</span> <span class="thinking-option-badge premium">Premium</span>
</div> </div>
<p class="thinking-option-desc">Głęboka analiza. Dla złożonych pytań, strategii, rekomendacji.</p> <p class="thinking-option-desc">Głęboka analiza i rozumowanie. Dla złożonych pytań, strategii, rekomendacji.</p>
<span class="thinking-option-price">~$0.20/pytanie</span> <span class="thinking-option-price">~$0.20/pytanie · limit: $2/dzień</span>
</div> </div>
</div> </div>
</div> </div>