From 26db9a7cc911d6e3524a9ea83fd37e785b9d0a7b Mon Sep 17 00:00:00 2001 From: Maciej Pienczyn Date: Thu, 29 Jan 2026 11:04:29 +0100 Subject: [PATCH] =?UTF-8?q?feat(chat):=20Wyb=C3=B3r=20modelu=20Flash/Pro?= =?UTF-8?q?=20zamiast=20Thinking=20Mode=20+=20koszt=20miesi=C4=99czny?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UI: - Dropdown wyboru modelu: Flash (szybki, $0.05) vs Pro (analiza, $0.20) - Wyświetlanie kosztu miesięcznego w headerze - Badge odpowiedzi pokazuje: model, czas, koszt Backend: - Endpoint /api/chat/settings obsługuje model i monthly_cost - NordaBizChatEngine przyjmuje parametr model - Koszt zapisywany w tech_info odpowiedzi Co-Authored-By: Claude Opus 4.5 --- app.py | 80 +++++++++------- nordabiz_chat.py | 18 +++- templates/chat.html | 227 +++++++++++++++++++++++++++++--------------- 3 files changed, 207 insertions(+), 118 deletions(-) diff --git a/app.py b/app.py index 93b5c06..1fdeddd 100644 --- a/app.py +++ b/app.py @@ -4517,33 +4517,51 @@ def chat(): @csrf.exempt @login_required def chat_settings(): - """Get or update chat settings (thinking level)""" + """Get or update chat settings (model selection, monthly cost)""" if request.method == 'GET': - # Get current thinking level from session or default - thinking_level = session.get('thinking_level', 'high') + # Get current model from session or default to flash + model = session.get('chat_model', 'flash') + + # Calculate monthly cost for current user + monthly_cost = 0.0 + try: + from database import AIApiCost + db = SessionLocal() + # Get first day of current month + first_day = datetime.now().replace(day=1, hour=0, minute=0, second=0, microsecond=0) + costs = db.query(AIApiCost).filter( + AIApiCost.user_id == current_user.id, + AIApiCost.timestamp >= first_day + ).all() + monthly_cost = sum(c.total_cost_usd or 0 for c in costs) + db.close() + except Exception as e: + logger.warning(f"Error calculating monthly cost: {e}") + return jsonify({ 'success': True, - 'thinking_level': thinking_level + 'model': model, + 'monthly_cost': round(monthly_cost, 4) }) # POST - update settings try: data = request.get_json() - thinking_level = data.get('thinking_level', 'high') + model = data.get('model', 'flash') - # Validate thinking level - valid_levels = ['minimal', 'low', 'medium', 'high'] - if thinking_level not in valid_levels: - thinking_level = 'high' + # Validate model + valid_models = ['flash', 'pro'] + if model not in valid_models: + model = 'flash' # Store in session - session['thinking_level'] = thinking_level + session['chat_model'] = model - logger.info(f"User {current_user.id} set thinking_level to: {thinking_level}") + logger.info(f"User {current_user.id} set chat_model to: {model}") return jsonify({ 'success': True, - 'thinking_level': thinking_level + 'model': model }) except Exception as e: @@ -4602,24 +4620,28 @@ def chat_send_message(conversation_id): finally: db.close() - # Get thinking level from request or session - thinking_level = data.get('thinking_level') or session.get('thinking_level', 'high') + # Get model from request or session (flash = default, pro = premium) + model_choice = data.get('model') or session.get('chat_model', 'flash') - chat_engine = NordaBizChatEngine() + # Map model choice to actual model name + model_map = { + 'flash': '3-flash', + 'pro': '3-pro' + } + model_key = model_map.get(model_choice, '3-flash') + + chat_engine = NordaBizChatEngine(model=model_key) response = chat_engine.send_message( conversation_id=conversation_id, user_message=message, user_id=current_user.id, - thinking_level=thinking_level + thinking_level='minimal' if model_choice == 'flash' else 'high' ) - # Get free tier usage stats for today - free_tier_stats = get_free_tier_usage() - - # Calculate theoretical cost (Gemini 2.0 Flash pricing) + # Get actual cost from response tokens_in = response.tokens_input or 0 tokens_out = response.tokens_output or 0 - theoretical_cost = (tokens_in / 1_000_000) * 0.075 + (tokens_out / 1_000_000) * 0.30 + actual_cost = response.cost_usd or 0.0 return jsonify({ 'success': True, @@ -4628,24 +4650,12 @@ def chat_send_message(conversation_id): 'created_at': response.created_at.isoformat(), # Technical metadata 'tech_info': { - 'model': gemini_service.get_gemini_service().model_name if gemini_service.get_gemini_service() else 'gemini-3-flash-preview', - 'thinking_level': thinking_level, - 'thinking_enabled': gemini_service.get_gemini_service().thinking_enabled if gemini_service.get_gemini_service() else True, - 'data_source': 'PostgreSQL (150 firm Norda Biznes)', - 'architecture': 'Full DB Context + Thinking Mode', + 'model': model_choice, 'tokens_input': tokens_in, 'tokens_output': tokens_out, 'tokens_total': tokens_in + tokens_out, 'latency_ms': response.latency_ms or 0, - 'theoretical_cost_usd': round(theoretical_cost, 6), - 'actual_cost_usd': 0.0, # Paid tier but tracked - 'free_tier': { - 'is_free': False, - 'daily_limit': 10000, # Gemini paid tier - 'requests_today': free_tier_stats['requests_today'], - 'tokens_today': free_tier_stats['tokens_today'], - 'remaining': max(0, 10000 - free_tier_stats['requests_today']) - } + 'cost_usd': round(actual_cost, 6) } }) diff --git a/nordabiz_chat.py b/nordabiz_chat.py index 613da82..d1ebd07 100644 --- a/nordabiz_chat.py +++ b/nordabiz_chat.py @@ -86,21 +86,29 @@ class NordaBizChatEngine: Helps users find companies, services, and business partners. """ - def __init__(self, gemini_api_key: Optional[str] = None, use_global_service: bool = True): + def __init__(self, gemini_api_key: Optional[str] = None, use_global_service: bool = True, model: str = None): """ Initialize Norda Biznes Chat Engine Args: gemini_api_key: Google Gemini API key (uses env var if not provided) use_global_service: Use global gemini_service for automatic cost tracking (default: True) + model: Model key ('3-flash', '3-pro') - if provided, creates new service with this model """ self.use_global_service = use_global_service + self.requested_model = model if use_global_service: - # Use global gemini_service for automatic cost tracking to ai_api_costs table - self.gemini_service = gemini_service.get_gemini_service() - # Get model name from global service (currently Gemini 3 Flash Preview) - self.model_name = self.gemini_service.model_name if self.gemini_service else "gemini-3-flash-preview" + if model: + # Create new service with requested model + from gemini_service import GeminiService + self.gemini_service = GeminiService(model=model) + self.model_name = self.gemini_service.model_name + else: + # Use global gemini_service for automatic cost tracking to ai_api_costs table + self.gemini_service = gemini_service.get_gemini_service() + # Get model name from global service (currently Gemini 3 Flash Preview) + self.model_name = self.gemini_service.model_name if self.gemini_service else "gemini-3-flash-preview" self.model = None # Initialize tokenizer for cost calculation (still needed for per-message tracking) diff --git a/templates/chat.html b/templates/chat.html index 0c25501..77d1c3b 100755 --- a/templates/chat.html +++ b/templates/chat.html @@ -311,6 +311,11 @@ font-style: italic; } + .thinking-badge-cost { + color: #f59e0b; + font-weight: 500; + } + /* Klikalne linki jako kolorowe badge'y */ .message-content a { display: inline-block; @@ -724,6 +729,49 @@ font-weight: 500; } + .thinking-option-badge.premium { + background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); + } + + .thinking-option-price { + display: block; + font-size: 11px; + color: #6b7280; + margin-top: 4px; + font-style: italic; + } + + /* Monthly Cost Badge */ + .monthly-cost-badge { + display: flex; + align-items: center; + gap: 6px; + background: rgba(255,255,255,0.15); + padding: 6px 12px; + border-radius: var(--radius-md); + font-size: 12px; + margin-left: var(--spacing-sm); + } + + .monthly-cost-badge .cost-icon { + font-size: 14px; + } + + .monthly-cost-badge .cost-label { + opacity: 0.8; + } + + .monthly-cost-badge .cost-value { + font-weight: 600; + color: #fef08a; + } + + @media (max-width: 768px) { + .monthly-cost-badge .cost-label { + display: none; + } + } + .thinking-option-desc { color: var(--text-secondary); font-size: var(--font-size-xs); @@ -1181,45 +1229,46 @@ 🤖

NordaGPT

- Gemini 3 - -
- -
+
- Tryb rozumowania AI -

Określa głębokość analizy przed odpowiedzią

+ Model AI +

Wybierz model dopasowany do pytania

-
+
- Błyskawiczny -
-

Najszybsze odpowiedzi. Dla prostych pytań typu "kto?", "gdzie?".

-
-
-
- 🚀 - Szybki -
-

Zrównoważony. Dobre dla większości pytań o firmy i usługi.

-
-
-
- 🧠 - Głęboki + Gemini Flash Domyślny
-

Maksymalna analiza. Dla złożonych pytań, rekomendacji, strategii.

+

Szybki i ekonomiczny. Dla prostych pytań o firmy, kontakty, wydarzenia.

+ ~$0.05/pytanie +
+
+
+ 🧠 + Gemini Pro + Premium +
+

Głęboka analiza. Dla złożonych pytań, strategii, rekomendacji.

+ ~$0.20/pytanie
+ +
+ 💰 + Ten miesiąc: + $0.00 +