feat(chat): Wybór modelu Flash/Pro zamiast Thinking Mode + koszt miesięczny
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 <noreply@anthropic.com>
This commit is contained in:
parent
fa696b331f
commit
26db9a7cc9
80
app.py
80
app.py
@ -4517,33 +4517,51 @@ def chat():
|
|||||||
@csrf.exempt
|
@csrf.exempt
|
||||||
@login_required
|
@login_required
|
||||||
def chat_settings():
|
def chat_settings():
|
||||||
"""Get or update chat settings (thinking level)"""
|
"""Get or update chat settings (model selection, monthly cost)"""
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
# Get current thinking level from session or default
|
# Get current model from session or default to flash
|
||||||
thinking_level = session.get('thinking_level', 'high')
|
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({
|
return jsonify({
|
||||||
'success': True,
|
'success': True,
|
||||||
'thinking_level': thinking_level
|
'model': model,
|
||||||
|
'monthly_cost': round(monthly_cost, 4)
|
||||||
})
|
})
|
||||||
|
|
||||||
# POST - update settings
|
# POST - update settings
|
||||||
try:
|
try:
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
thinking_level = data.get('thinking_level', 'high')
|
model = data.get('model', 'flash')
|
||||||
|
|
||||||
# Validate thinking level
|
# Validate model
|
||||||
valid_levels = ['minimal', 'low', 'medium', 'high']
|
valid_models = ['flash', 'pro']
|
||||||
if thinking_level not in valid_levels:
|
if model not in valid_models:
|
||||||
thinking_level = 'high'
|
model = 'flash'
|
||||||
|
|
||||||
# Store in session
|
# 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({
|
return jsonify({
|
||||||
'success': True,
|
'success': True,
|
||||||
'thinking_level': thinking_level
|
'model': model
|
||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -4602,24 +4620,28 @@ def chat_send_message(conversation_id):
|
|||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
# Get thinking level from request or session
|
# Get model from request or session (flash = default, pro = premium)
|
||||||
thinking_level = data.get('thinking_level') or session.get('thinking_level', 'high')
|
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(
|
response = chat_engine.send_message(
|
||||||
conversation_id=conversation_id,
|
conversation_id=conversation_id,
|
||||||
user_message=message,
|
user_message=message,
|
||||||
user_id=current_user.id,
|
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
|
# Get actual cost from response
|
||||||
free_tier_stats = get_free_tier_usage()
|
|
||||||
|
|
||||||
# Calculate theoretical cost (Gemini 2.0 Flash pricing)
|
|
||||||
tokens_in = response.tokens_input or 0
|
tokens_in = response.tokens_input or 0
|
||||||
tokens_out = response.tokens_output 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({
|
return jsonify({
|
||||||
'success': True,
|
'success': True,
|
||||||
@ -4628,24 +4650,12 @@ def chat_send_message(conversation_id):
|
|||||||
'created_at': response.created_at.isoformat(),
|
'created_at': response.created_at.isoformat(),
|
||||||
# Technical metadata
|
# Technical metadata
|
||||||
'tech_info': {
|
'tech_info': {
|
||||||
'model': gemini_service.get_gemini_service().model_name if gemini_service.get_gemini_service() else 'gemini-3-flash-preview',
|
'model': model_choice,
|
||||||
'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',
|
|
||||||
'tokens_input': tokens_in,
|
'tokens_input': tokens_in,
|
||||||
'tokens_output': tokens_out,
|
'tokens_output': tokens_out,
|
||||||
'tokens_total': tokens_in + tokens_out,
|
'tokens_total': tokens_in + tokens_out,
|
||||||
'latency_ms': response.latency_ms or 0,
|
'latency_ms': response.latency_ms or 0,
|
||||||
'theoretical_cost_usd': round(theoretical_cost, 6),
|
'cost_usd': round(actual_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'])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -86,21 +86,29 @@ class NordaBizChatEngine:
|
|||||||
Helps users find companies, services, and business partners.
|
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
|
Initialize Norda Biznes Chat Engine
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
gemini_api_key: Google Gemini API key (uses env var if not provided)
|
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)
|
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.use_global_service = use_global_service
|
||||||
|
self.requested_model = model
|
||||||
|
|
||||||
if use_global_service:
|
if use_global_service:
|
||||||
# Use global gemini_service for automatic cost tracking to ai_api_costs table
|
if model:
|
||||||
self.gemini_service = gemini_service.get_gemini_service()
|
# Create new service with requested model
|
||||||
# Get model name from global service (currently Gemini 3 Flash Preview)
|
from gemini_service import GeminiService
|
||||||
self.model_name = self.gemini_service.model_name if self.gemini_service else "gemini-3-flash-preview"
|
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
|
self.model = None
|
||||||
|
|
||||||
# Initialize tokenizer for cost calculation (still needed for per-message tracking)
|
# Initialize tokenizer for cost calculation (still needed for per-message tracking)
|
||||||
|
|||||||
@ -311,6 +311,11 @@
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.thinking-badge-cost {
|
||||||
|
color: #f59e0b;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
/* Klikalne linki jako kolorowe badge'y */
|
/* Klikalne linki jako kolorowe badge'y */
|
||||||
.message-content a {
|
.message-content a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -724,6 +729,49 @@
|
|||||||
font-weight: 500;
|
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 {
|
.thinking-option-desc {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-xs);
|
||||||
@ -1181,45 +1229,46 @@
|
|||||||
</button>
|
</button>
|
||||||
<span style="font-size: 1.5rem;">🤖</span>
|
<span style="font-size: 1.5rem;">🤖</span>
|
||||||
<h1>NordaGPT</h1>
|
<h1>NordaGPT</h1>
|
||||||
<span class="chat-header-badge">Gemini 3</span>
|
<!-- Model Selection Toggle -->
|
||||||
<!-- Thinking Mode Toggle -->
|
<div class="thinking-toggle" id="modelToggle">
|
||||||
<div class="thinking-toggle" id="thinkingToggle">
|
<button class="thinking-btn" onclick="toggleModelDropdown()" title="Wybierz model AI">
|
||||||
<button class="thinking-btn" onclick="toggleThinkingDropdown()" title="Tryb rozumowania AI">
|
<span class="thinking-icon" id="modelIcon">⚡</span>
|
||||||
<span class="thinking-icon">🧠</span>
|
<span class="thinking-label" id="modelLabel">Flash</span>
|
||||||
<span class="thinking-label" id="thinkingLabel">Wysoki</span>
|
|
||||||
<svg class="thinking-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="12" height="12">
|
<svg class="thinking-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="12" height="12">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div class="thinking-dropdown" id="thinkingDropdown">
|
<div class="thinking-dropdown" id="modelDropdown">
|
||||||
<div class="thinking-dropdown-header">
|
<div class="thinking-dropdown-header">
|
||||||
<strong>Tryb rozumowania AI</strong>
|
<strong>Model AI</strong>
|
||||||
<p>Określa głębokość analizy przed odpowiedzią</p>
|
<p>Wybierz model dopasowany do pytania</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="thinking-option" data-level="minimal" onclick="setThinkingLevel('minimal')">
|
<div class="thinking-option active" data-model="flash" onclick="setModel('flash')">
|
||||||
<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">Błyskawiczny</span>
|
<span class="thinking-option-name">Gemini Flash</span>
|
||||||
</div>
|
|
||||||
<p class="thinking-option-desc">Najszybsze odpowiedzi. Dla prostych pytań typu "kto?", "gdzie?".</p>
|
|
||||||
</div>
|
|
||||||
<div class="thinking-option" data-level="low" onclick="setThinkingLevel('low')">
|
|
||||||
<div class="thinking-option-header">
|
|
||||||
<span class="thinking-option-icon">🚀</span>
|
|
||||||
<span class="thinking-option-name">Szybki</span>
|
|
||||||
</div>
|
|
||||||
<p class="thinking-option-desc">Zrównoważony. Dobre dla większości pytań o firmy i usługi.</p>
|
|
||||||
</div>
|
|
||||||
<div class="thinking-option active" data-level="high" onclick="setThinkingLevel('high')">
|
|
||||||
<div class="thinking-option-header">
|
|
||||||
<span class="thinking-option-icon">🧠</span>
|
|
||||||
<span class="thinking-option-name">Głęboki</span>
|
|
||||||
<span class="thinking-option-badge">Domyślny</span>
|
<span class="thinking-option-badge">Domyślny</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="thinking-option-desc">Maksymalna analiza. Dla złożonych pytań, rekomendacji, strategii.</p>
|
<p class="thinking-option-desc">Szybki i ekonomiczny. Dla prostych pytań o firmy, kontakty, wydarzenia.</p>
|
||||||
|
<span class="thinking-option-price">~$0.05/pytanie</span>
|
||||||
|
</div>
|
||||||
|
<div class="thinking-option" data-model="pro" onclick="setModel('pro')">
|
||||||
|
<div class="thinking-option-header">
|
||||||
|
<span class="thinking-option-icon">🧠</span>
|
||||||
|
<span class="thinking-option-name">Gemini Pro</span>
|
||||||
|
<span class="thinking-option-badge premium">Premium</span>
|
||||||
|
</div>
|
||||||
|
<p class="thinking-option-desc">Głęboka analiza. Dla złożonych pytań, strategii, rekomendacji.</p>
|
||||||
|
<span class="thinking-option-price">~$0.20/pytanie</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Monthly cost display -->
|
||||||
|
<div class="monthly-cost-badge" title="Twoje koszty AI w tym miesiącu">
|
||||||
|
<span class="cost-icon">💰</span>
|
||||||
|
<span class="cost-label">Ten miesiąc:</span>
|
||||||
|
<span class="cost-value" id="monthlyCostDisplay">$0.00</span>
|
||||||
|
</div>
|
||||||
<button class="model-info-btn" onclick="openModelInfoModal()" title="Informacje o modelu AI">
|
<button class="model-info-btn" onclick="openModelInfoModal()" title="Informacje o modelu AI">
|
||||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" width="16" height="16">
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" width="16" height="16">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||||
@ -1431,57 +1480,67 @@
|
|||||||
// NordaGPT Chat - State
|
// NordaGPT Chat - State
|
||||||
let currentConversationId = null;
|
let currentConversationId = null;
|
||||||
let conversations = [];
|
let conversations = [];
|
||||||
let currentThinkingLevel = 'high'; // Default thinking level
|
let currentModel = 'flash'; // Default model (flash = ekonomiczny)
|
||||||
|
let monthlyUsageCost = 0; // Koszt miesięczny użytkownika
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// Thinking Mode Toggle Functions
|
// Model Selection Toggle Functions
|
||||||
// ============================================
|
// ============================================
|
||||||
const THINKING_LABELS = {
|
const MODEL_CONFIG = {
|
||||||
'minimal': 'Błyskawiczny',
|
'flash': { label: 'Flash', icon: '⚡', desc: 'Szybki' },
|
||||||
'low': 'Szybki',
|
'pro': { label: 'Pro', icon: '🧠', desc: 'Analiza' }
|
||||||
'high': 'Głęboki'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function toggleThinkingDropdown() {
|
function toggleModelDropdown() {
|
||||||
const toggle = document.getElementById('thinkingToggle');
|
const toggle = document.getElementById('modelToggle');
|
||||||
toggle.classList.toggle('open');
|
toggle.classList.toggle('open');
|
||||||
}
|
}
|
||||||
|
|
||||||
function setThinkingLevel(level) {
|
function setModel(model) {
|
||||||
currentThinkingLevel = level;
|
currentModel = model;
|
||||||
|
const config = MODEL_CONFIG[model];
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
document.getElementById('thinkingLabel').textContent = THINKING_LABELS[level] || level;
|
document.getElementById('modelLabel').textContent = config.label;
|
||||||
|
document.getElementById('modelIcon').textContent = config.icon;
|
||||||
|
|
||||||
// Update active state
|
// Update active state
|
||||||
document.querySelectorAll('.thinking-option').forEach(opt => {
|
document.querySelectorAll('.thinking-option').forEach(opt => {
|
||||||
opt.classList.toggle('active', opt.dataset.level === level);
|
opt.classList.toggle('active', opt.dataset.model === model);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close dropdown
|
// Close dropdown
|
||||||
document.getElementById('thinkingToggle').classList.remove('open');
|
document.getElementById('modelToggle').classList.remove('open');
|
||||||
|
|
||||||
// Save preference to server
|
// Save preference to server
|
||||||
saveThinkingPreference(level);
|
saveModelPreference(model);
|
||||||
|
|
||||||
console.log('Thinking level set to:', level);
|
console.log('Model set to:', model);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveThinkingPreference(level) {
|
async function saveModelPreference(model) {
|
||||||
try {
|
try {
|
||||||
await fetch('/api/chat/settings', {
|
await fetch('/api/chat/settings', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ thinking_level: level })
|
body: JSON.stringify({ model: model })
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to save thinking preference:', error);
|
console.error('Failed to save model preference:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close thinking dropdown when clicking outside
|
function updateMonthlyCost(cost) {
|
||||||
|
monthlyUsageCost += cost;
|
||||||
|
const costDisplay = document.getElementById('monthlyCostDisplay');
|
||||||
|
if (costDisplay) {
|
||||||
|
costDisplay.textContent = '$' + monthlyUsageCost.toFixed(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close model dropdown when clicking outside
|
||||||
document.addEventListener('click', function(e) {
|
document.addEventListener('click', function(e) {
|
||||||
const toggle = document.getElementById('thinkingToggle');
|
const toggle = document.getElementById('modelToggle');
|
||||||
if (toggle && !toggle.contains(e.target)) {
|
if (toggle && !toggle.contains(e.target)) {
|
||||||
toggle.classList.remove('open');
|
toggle.classList.remove('open');
|
||||||
}
|
}
|
||||||
@ -1552,23 +1611,33 @@ document.addEventListener('click', function(e) {
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
loadConversations();
|
loadConversations();
|
||||||
autoResizeTextarea();
|
autoResizeTextarea();
|
||||||
loadThinkingSettings();
|
loadModelSettings();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load thinking settings from server
|
// Load model settings and monthly cost from server
|
||||||
async function loadThinkingSettings() {
|
async function loadModelSettings() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/chat/settings');
|
const response = await fetch('/api/chat/settings');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.success && data.thinking_level) {
|
if (data.success) {
|
||||||
currentThinkingLevel = data.thinking_level;
|
// Load model preference
|
||||||
document.getElementById('thinkingLabel').textContent = THINKING_LABELS[data.thinking_level] || data.thinking_level;
|
if (data.model) {
|
||||||
document.querySelectorAll('.thinking-option').forEach(opt => {
|
currentModel = data.model;
|
||||||
opt.classList.toggle('active', opt.dataset.level === data.thinking_level);
|
const config = MODEL_CONFIG[data.model] || MODEL_CONFIG['flash'];
|
||||||
});
|
document.getElementById('modelLabel').textContent = config.label;
|
||||||
|
document.getElementById('modelIcon').textContent = config.icon;
|
||||||
|
document.querySelectorAll('.thinking-option').forEach(opt => {
|
||||||
|
opt.classList.toggle('active', opt.dataset.model === data.model);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Load monthly cost
|
||||||
|
if (data.monthly_cost !== undefined) {
|
||||||
|
monthlyUsageCost = data.monthly_cost;
|
||||||
|
document.getElementById('monthlyCostDisplay').textContent = '$' + monthlyUsageCost.toFixed(2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Using default thinking level:', currentThinkingLevel);
|
console.log('Using default model:', currentModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1769,13 +1838,13 @@ async function sendMessage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send message with thinking level
|
// Send message with model selection
|
||||||
const response = await fetch(`/api/chat/${currentConversationId}/message`, {
|
const response = await fetch(`/api/chat/${currentConversationId}/message`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
message: message,
|
message: message,
|
||||||
thinking_level: currentThinkingLevel
|
model: currentModel
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@ -1826,33 +1895,35 @@ function addMessage(role, content, animate = true, techInfo = null) {
|
|||||||
contentDiv.className = 'message-content';
|
contentDiv.className = 'message-content';
|
||||||
contentDiv.innerHTML = formatMessage(content);
|
contentDiv.innerHTML = formatMessage(content);
|
||||||
|
|
||||||
// Add thinking info badge for AI responses
|
// Add response info badge for AI responses (model, time, cost)
|
||||||
if (role === 'assistant' && techInfo) {
|
if (role === 'assistant' && techInfo) {
|
||||||
const thinkingBadge = document.createElement('div');
|
const infoBadge = document.createElement('div');
|
||||||
thinkingBadge.className = 'thinking-info-badge';
|
infoBadge.className = 'thinking-info-badge';
|
||||||
|
|
||||||
const thinkingLevel = techInfo.thinking_level || 'high';
|
const modelName = techInfo.model || 'flash';
|
||||||
const latencyMs = techInfo.latency_ms || 0;
|
const latencyMs = techInfo.latency_ms || 0;
|
||||||
const latencySec = (latencyMs / 1000).toFixed(1);
|
const latencySec = (latencyMs / 1000).toFixed(1);
|
||||||
|
const costUsd = techInfo.cost_usd || 0;
|
||||||
|
|
||||||
// Labels with quality descriptions to show value of deeper thinking
|
// Model labels
|
||||||
const levelLabels = {
|
const modelLabels = {
|
||||||
'minimal': '⚡ Błyskawiczny',
|
'flash': '⚡ Flash',
|
||||||
'low': '🚀 Szybki',
|
'pro': '🧠 Pro',
|
||||||
'medium': '⚖️ Zbalansowany',
|
'gemini-3-flash-preview': '⚡ Flash',
|
||||||
'high': '🧠 Głęboka analiza'
|
'gemini-3-pro-preview': '🧠 Pro'
|
||||||
};
|
};
|
||||||
const levelDescriptions = {
|
const modelLabel = modelLabels[modelName] || modelName;
|
||||||
'minimal': 'szybka odpowiedź',
|
|
||||||
'low': 'zwięzła analiza',
|
|
||||||
'medium': 'przemyślana odpowiedź',
|
|
||||||
'high': 'dogłębna analiza z weryfikacją'
|
|
||||||
};
|
|
||||||
const levelLabel = levelLabels[thinkingLevel] || thinkingLevel;
|
|
||||||
const levelDesc = levelDescriptions[thinkingLevel] || '';
|
|
||||||
|
|
||||||
thinkingBadge.innerHTML = `<span class="thinking-badge-level">${levelLabel}</span> · <span class="thinking-badge-desc">${levelDesc}</span> · <span class="thinking-badge-time">${latencySec}s</span>`;
|
// Format cost
|
||||||
contentDiv.appendChild(thinkingBadge);
|
const costStr = costUsd > 0 ? `$${costUsd.toFixed(4)}` : '$0.00';
|
||||||
|
|
||||||
|
infoBadge.innerHTML = `<span class="thinking-badge-level">${modelLabel}</span> · <span class="thinking-badge-time">${latencySec}s</span> · <span class="thinking-badge-cost">${costStr}</span>`;
|
||||||
|
contentDiv.appendChild(infoBadge);
|
||||||
|
|
||||||
|
// Update monthly cost if cost provided
|
||||||
|
if (costUsd > 0) {
|
||||||
|
updateMonthlyCost(costUsd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
messageDiv.appendChild(avatar);
|
messageDiv.appendChild(avatar);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user