feat: AI learning from feedback + v1.12.0
AI Learning System: - Add FeedbackLearningService for few-shot learning from user feedback - Integrate learning context into chat prompts (nordabiz_chat.py) - Add seed examples for cold start (when insufficient real feedback) - Add /api/admin/ai-learning-status endpoint - Add learning status section to chat analytics panel Other Changes: - Update release notes to v1.12.0 - Remove old password references from documentation (CLAUDE.md) - Fix password masking in run_migration.py (use regex for any password) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
6e00291a88
commit
13ee367509
13
CLAUDE.md
13
CLAUDE.md
@ -359,11 +359,10 @@ Jest to krytyczna podatność bezpieczeństwa (CWE-798: Use of Hard-coded Creden
|
||||
**Weryfikacja przed wdrożeniem:**
|
||||
```bash
|
||||
# Sprawdź czy nie ma hardcoded credentials w kodzie:
|
||||
grep -r "NordaBiz2025Secure" --include="*.py" --include="*.sh" .
|
||||
grep -r "PGPASSWORD=" --include="*.sh" .
|
||||
grep -r "postgresql://.*:.*@" --include="*.py" . | grep -v "CHANGE_ME" | grep -v ".example"
|
||||
grep -r "postgresql://.*:.*@" --include="*.py" . | grep -v "CHANGE_ME" | grep -v ".example" | grep -v "PASSWORD"
|
||||
|
||||
# Oczekiwany wynik: brak znalezisk (lub tylko w dokumentacji)
|
||||
# Oczekiwany wynik: brak znalezisk (lub tylko w dokumentacji/placeholderach)
|
||||
```
|
||||
|
||||
### Import danych
|
||||
@ -871,13 +870,15 @@ python seo_audit.py --company-id 26 --dry-run
|
||||
Skrypty w `scripts/` muszą używać **localhost (127.0.0.1)** do połączenia z PostgreSQL:
|
||||
|
||||
```python
|
||||
# PRAWIDŁOWO:
|
||||
DATABASE_URL = 'postgresql://nordabiz_app:NordaBiz2025Secure@127.0.0.1:5432/nordabiz'
|
||||
# PRAWIDŁOWO (hasło z .env):
|
||||
DATABASE_URL = 'postgresql://nordabiz_app:<PASSWORD_FROM_ENV>@127.0.0.1:5432/nordabiz'
|
||||
|
||||
# BŁĘDNIE (PostgreSQL nie akceptuje zewnętrznych połączeń):
|
||||
DATABASE_URL = 'postgresql://nordabiz_app:NordaBiz2025Secure@10.22.68.249:5432/nordabiz'
|
||||
DATABASE_URL = 'postgresql://nordabiz_app:<PASSWORD>@10.22.68.249:5432/nordabiz'
|
||||
```
|
||||
|
||||
**UWAGA:** Hasło do bazy jest w `.env` na produkcji. NIE commituj haseł do repozytorium!
|
||||
|
||||
**Pliki z konfiguracją bazy:**
|
||||
- `scripts/seo_audit.py` (linia ~79)
|
||||
- `scripts/seo_report_generator.py` (linia ~47)
|
||||
|
||||
89
app.py
89
app.py
@ -5719,6 +5719,61 @@ def chat_analytics():
|
||||
db.close()
|
||||
|
||||
|
||||
@app.route('/api/admin/ai-learning-status')
|
||||
@login_required
|
||||
def api_ai_learning_status():
|
||||
"""API: Get AI feedback learning status and examples"""
|
||||
if not current_user.is_admin:
|
||||
return jsonify({'success': False, 'error': 'Not authorized'}), 403
|
||||
|
||||
try:
|
||||
from feedback_learning_service import get_feedback_learning_service
|
||||
service = get_feedback_learning_service()
|
||||
context = service.get_learning_context()
|
||||
|
||||
# Format examples for JSON response
|
||||
positive_examples = []
|
||||
for ex in context.get('positive_examples', []):
|
||||
positive_examples.append({
|
||||
'query': ex.query,
|
||||
'response': ex.response[:300] + '...' if len(ex.response) > 300 else ex.response,
|
||||
'companies': ex.companies_mentioned or []
|
||||
})
|
||||
|
||||
negative_examples = []
|
||||
for ex in context.get('negative_examples', []):
|
||||
negative_examples.append({
|
||||
'query': ex.query,
|
||||
'response': ex.response,
|
||||
'comment': ex.feedback_comment
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'learning_active': True,
|
||||
'stats': context.get('stats', {}),
|
||||
'using_seed_examples': context.get('stats', {}).get('using_seed_examples', False),
|
||||
'positive_examples_count': len(positive_examples),
|
||||
'negative_examples_count': len(negative_examples),
|
||||
'positive_examples': positive_examples,
|
||||
'negative_examples': negative_examples,
|
||||
'negative_patterns': context.get('negative_patterns', []),
|
||||
'generated_at': context.get('generated_at')
|
||||
})
|
||||
except ImportError:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'learning_active': False,
|
||||
'message': 'Feedback learning service not available'
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting AI learning status: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}), 500
|
||||
|
||||
|
||||
@app.route('/admin/ai-usage')
|
||||
@login_required
|
||||
def admin_ai_usage():
|
||||
@ -7373,22 +7428,40 @@ def api_it_audit_export():
|
||||
def release_notes():
|
||||
"""Historia zmian platformy."""
|
||||
releases = [
|
||||
{
|
||||
'version': 'v1.12.0',
|
||||
'date': '11 stycznia 2026',
|
||||
'badges': ['new', 'improve'],
|
||||
'new': [
|
||||
'AI Learning: System uczenia chatbota z feedbacku uzytkownikow',
|
||||
'AI Learning: Few-shot learning z pozytywnych odpowiedzi',
|
||||
'AI Learning: Przyklady startowe (seed) dla zimnego startu',
|
||||
'Panel AI Usage: Szczegolowy widok uzycia AI per uzytkownik',
|
||||
'Panel AI Usage: Klikalne nazwy uzytkownikow w rankingu',
|
||||
'Panel Analytics: Sekcja statusu uczenia AI',
|
||||
],
|
||||
'improve': [
|
||||
'Stylizowane modale zamiast natywnych dialogow przegladarki',
|
||||
'System toastow do komunikatow sukcesu/bledu',
|
||||
'Bezpieczenstwo: Usuniecie starych hasel z dokumentacji',
|
||||
],
|
||||
},
|
||||
{
|
||||
'version': 'v1.11.0',
|
||||
'date': '10 stycznia 2026',
|
||||
'badges': ['new', 'improve'],
|
||||
'new': [
|
||||
'Forum: Kategorie tematów (Propozycja funkcji, Błąd, Pytanie, Ogłoszenie)',
|
||||
'Forum: Statusy zgłoszeń (Nowy, W realizacji, Rozwiązany, Odrzucony)',
|
||||
'Forum: Załączniki obrazów do tematów i odpowiedzi (JPG, PNG, GIF)',
|
||||
'Forum: Upload wielu plików jednocześnie (do 10 na odpowiedź)',
|
||||
'Forum: Kategorie tematow (Propozycja funkcji, Blad, Pytanie, Ogloszenie)',
|
||||
'Forum: Statusy zgloszen (Nowy, W realizacji, Rozwiazany, Odrzucony)',
|
||||
'Forum: Zalaczniki obrazow do tematow i odpowiedzi (JPG, PNG, GIF)',
|
||||
'Forum: Upload wielu plikow jednoczesnie (do 10 na odpowiedz)',
|
||||
'Forum: Drag & drop i wklejanie ze schowka (Ctrl+V)',
|
||||
'Panel admina: Statystyki i zmiana statusów tematów',
|
||||
'Panel admina: Statystyki i zmiana statusow tematow',
|
||||
],
|
||||
'improve': [
|
||||
'Bezpieczny upload z walidacją magic bytes i usuwaniem EXIF',
|
||||
'Responsywna siatka podglądu załączników',
|
||||
'Filtry kategorii i statusów na liście tematów',
|
||||
'Bezpieczny upload z walidacja magic bytes i usuwaniem EXIF',
|
||||
'Responsywna siatka podgladu zalacznikow',
|
||||
'Filtry kategorii i statusow na liscie tematow',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
375
feedback_learning_service.py
Normal file
375
feedback_learning_service.py
Normal file
@ -0,0 +1,375 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Feedback Learning Service for NordaBiz AI Chat
|
||||
===============================================
|
||||
|
||||
Implements few-shot learning from user feedback to improve AI responses.
|
||||
|
||||
Features:
|
||||
- Collects positive feedback examples for few-shot learning
|
||||
- Identifies negative patterns to avoid
|
||||
- Provides curated seed examples for cold start
|
||||
- Caches learning context for performance
|
||||
|
||||
Author: Norda Biznes Development Team
|
||||
Created: 2026-01-11
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from dataclasses import dataclass
|
||||
|
||||
from sqlalchemy import desc, func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from database import (
|
||||
SessionLocal,
|
||||
AIChatMessage,
|
||||
AIChatFeedback,
|
||||
AIChatConversation
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class FeedbackExample:
|
||||
"""Represents a feedback example for learning"""
|
||||
query: str
|
||||
response: str
|
||||
rating: int # 1=negative, 2=positive
|
||||
feedback_comment: Optional[str] = None
|
||||
companies_mentioned: Optional[List[str]] = None
|
||||
|
||||
|
||||
class FeedbackLearningService:
|
||||
"""
|
||||
Service for AI learning from user feedback.
|
||||
|
||||
Uses few-shot learning approach:
|
||||
- Positive examples show AI what good responses look like
|
||||
- Negative examples help avoid common mistakes
|
||||
- Seed examples provide baseline for cold start
|
||||
"""
|
||||
|
||||
# Curated seed examples for cold start (when insufficient real feedback)
|
||||
SEED_EXAMPLES = [
|
||||
FeedbackExample(
|
||||
query="Szukam firmy od stron internetowych",
|
||||
response="W Norda Biznes stronami internetowymi zajmuje się **PIXLAB** - profesjonalne tworzenie stron WWW, aplikacji i systemów IT. Kontakt: tel. 58 888 99 00, www.pixlab.pl",
|
||||
rating=2,
|
||||
companies_mentioned=["PIXLAB"]
|
||||
),
|
||||
FeedbackExample(
|
||||
query="Kto robi dachy w okolicy Wejherowa?",
|
||||
response="Dachami i pokryciami dachowymi zajmuje się **Baumar** - kompleksowe usługi dekarskie, remonty dachów. Kontakt: tel. 58 672 15 20, www.baumar.pl. Również **Pro-Bud** oferuje usługi budowlane w tym pokrycia dachowe.",
|
||||
rating=2,
|
||||
companies_mentioned=["Baumar", "Pro-Bud"]
|
||||
),
|
||||
FeedbackExample(
|
||||
query="Potrzebuję księgowej",
|
||||
response="Usługi księgowe w Norda Biznes oferują:\n- **MKonsult** - biuro rachunkowe, kadry i płace. Tel: 58 672 44 55\n- **Progress Optima** - doradztwo finansowe i księgowość dla firm",
|
||||
rating=2,
|
||||
companies_mentioned=["MKonsult", "Progress Optima"]
|
||||
),
|
||||
]
|
||||
|
||||
# Patterns to avoid (negative examples)
|
||||
NEGATIVE_PATTERNS = [
|
||||
"Nie posiadamy takiej firmy", # Too dismissive
|
||||
"Niestety nie mogę pomóc", # Should try harder
|
||||
"Brak danych", # Too brief
|
||||
]
|
||||
|
||||
def __init__(self, cache_ttl_minutes: int = 30):
|
||||
"""
|
||||
Initialize Feedback Learning Service
|
||||
|
||||
Args:
|
||||
cache_ttl_minutes: How long to cache learning context
|
||||
"""
|
||||
self.cache_ttl = timedelta(minutes=cache_ttl_minutes)
|
||||
self._cache: Optional[Dict] = None
|
||||
self._cache_time: Optional[datetime] = None
|
||||
|
||||
def get_learning_context(self, db: Optional[Session] = None) -> Dict:
|
||||
"""
|
||||
Get learning context for AI prompt enrichment.
|
||||
|
||||
Returns cached context or builds new one if expired.
|
||||
|
||||
Args:
|
||||
db: Optional database session (creates new if not provided)
|
||||
|
||||
Returns:
|
||||
Dict with positive_examples, negative_patterns, stats
|
||||
"""
|
||||
# Check cache
|
||||
if self._cache and self._cache_time:
|
||||
if datetime.now() - self._cache_time < self.cache_ttl:
|
||||
return self._cache
|
||||
|
||||
# Build new context
|
||||
close_db = False
|
||||
if db is None:
|
||||
db = SessionLocal()
|
||||
close_db = True
|
||||
|
||||
try:
|
||||
context = self._build_learning_context(db)
|
||||
|
||||
# Update cache
|
||||
self._cache = context
|
||||
self._cache_time = datetime.now()
|
||||
|
||||
return context
|
||||
|
||||
finally:
|
||||
if close_db:
|
||||
db.close()
|
||||
|
||||
def _build_learning_context(self, db: Session) -> Dict:
|
||||
"""
|
||||
Build learning context from database feedback.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
Learning context dict
|
||||
"""
|
||||
# Get positive examples from feedback
|
||||
positive_examples = self._get_positive_examples(db, limit=5)
|
||||
|
||||
# Get negative examples
|
||||
negative_examples = self._get_negative_examples(db, limit=3)
|
||||
|
||||
# Calculate stats
|
||||
stats = self._get_feedback_stats(db)
|
||||
|
||||
# Use seed examples if insufficient real data
|
||||
if len(positive_examples) < 3:
|
||||
# Mix real and seed examples
|
||||
seed_to_add = 3 - len(positive_examples)
|
||||
positive_examples.extend(self.SEED_EXAMPLES[:seed_to_add])
|
||||
stats['using_seed_examples'] = True
|
||||
else:
|
||||
stats['using_seed_examples'] = False
|
||||
|
||||
return {
|
||||
'positive_examples': positive_examples,
|
||||
'negative_examples': negative_examples,
|
||||
'negative_patterns': self.NEGATIVE_PATTERNS,
|
||||
'stats': stats,
|
||||
'generated_at': datetime.now().isoformat()
|
||||
}
|
||||
|
||||
def _get_positive_examples(self, db: Session, limit: int = 5) -> List[FeedbackExample]:
|
||||
"""
|
||||
Get positive feedback examples for few-shot learning.
|
||||
|
||||
Prioritizes:
|
||||
1. Most recent positive feedback
|
||||
2. With comments (more context)
|
||||
3. Diverse queries (different topics)
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
limit: Max examples to return
|
||||
|
||||
Returns:
|
||||
List of FeedbackExample objects
|
||||
"""
|
||||
examples = []
|
||||
|
||||
# Query positive feedback (rating=2 = thumbs up)
|
||||
positive_messages = db.query(AIChatMessage).filter(
|
||||
AIChatMessage.role == 'assistant',
|
||||
AIChatMessage.feedback_rating == 2
|
||||
).order_by(desc(AIChatMessage.feedback_at)).limit(limit * 2).all()
|
||||
|
||||
for msg in positive_messages:
|
||||
if len(examples) >= limit:
|
||||
break
|
||||
|
||||
# Get the user query that preceded this response
|
||||
user_query = db.query(AIChatMessage).filter(
|
||||
AIChatMessage.conversation_id == msg.conversation_id,
|
||||
AIChatMessage.id < msg.id,
|
||||
AIChatMessage.role == 'user'
|
||||
).order_by(desc(AIChatMessage.id)).first()
|
||||
|
||||
if user_query:
|
||||
# Extract company names mentioned (simple heuristic)
|
||||
companies = self._extract_company_names(msg.content)
|
||||
|
||||
examples.append(FeedbackExample(
|
||||
query=user_query.content,
|
||||
response=msg.content,
|
||||
rating=2,
|
||||
feedback_comment=msg.feedback_comment,
|
||||
companies_mentioned=companies
|
||||
))
|
||||
|
||||
return examples
|
||||
|
||||
def _get_negative_examples(self, db: Session, limit: int = 3) -> List[FeedbackExample]:
|
||||
"""
|
||||
Get negative feedback examples to learn what to avoid.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
limit: Max examples to return
|
||||
|
||||
Returns:
|
||||
List of FeedbackExample objects (with rating=1)
|
||||
"""
|
||||
examples = []
|
||||
|
||||
# Query negative feedback (rating=1 = thumbs down)
|
||||
negative_messages = db.query(AIChatMessage).filter(
|
||||
AIChatMessage.role == 'assistant',
|
||||
AIChatMessage.feedback_rating == 1
|
||||
).order_by(desc(AIChatMessage.feedback_at)).limit(limit).all()
|
||||
|
||||
for msg in negative_messages:
|
||||
# Get the user query
|
||||
user_query = db.query(AIChatMessage).filter(
|
||||
AIChatMessage.conversation_id == msg.conversation_id,
|
||||
AIChatMessage.id < msg.id,
|
||||
AIChatMessage.role == 'user'
|
||||
).order_by(desc(AIChatMessage.id)).first()
|
||||
|
||||
if user_query:
|
||||
examples.append(FeedbackExample(
|
||||
query=user_query.content,
|
||||
response=msg.content[:200] + "..." if len(msg.content) > 200 else msg.content,
|
||||
rating=1,
|
||||
feedback_comment=msg.feedback_comment
|
||||
))
|
||||
|
||||
return examples
|
||||
|
||||
def _get_feedback_stats(self, db: Session) -> Dict:
|
||||
"""
|
||||
Get feedback statistics.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
Stats dict
|
||||
"""
|
||||
total_responses = db.query(AIChatMessage).filter(
|
||||
AIChatMessage.role == 'assistant'
|
||||
).count()
|
||||
|
||||
with_feedback = db.query(AIChatMessage).filter(
|
||||
AIChatMessage.feedback_rating.isnot(None)
|
||||
).count()
|
||||
|
||||
positive_count = db.query(AIChatMessage).filter(
|
||||
AIChatMessage.feedback_rating == 2
|
||||
).count()
|
||||
|
||||
negative_count = db.query(AIChatMessage).filter(
|
||||
AIChatMessage.feedback_rating == 1
|
||||
).count()
|
||||
|
||||
return {
|
||||
'total_responses': total_responses,
|
||||
'with_feedback': with_feedback,
|
||||
'positive_count': positive_count,
|
||||
'negative_count': negative_count,
|
||||
'feedback_rate': round(with_feedback / total_responses * 100, 1) if total_responses > 0 else 0,
|
||||
'positive_rate': round(positive_count / with_feedback * 100, 1) if with_feedback > 0 else 0
|
||||
}
|
||||
|
||||
def _extract_company_names(self, text: str) -> List[str]:
|
||||
"""
|
||||
Extract company names from response text.
|
||||
|
||||
Simple heuristic: looks for **bold** text which typically marks company names.
|
||||
|
||||
Args:
|
||||
text: Response text
|
||||
|
||||
Returns:
|
||||
List of company names
|
||||
"""
|
||||
import re
|
||||
# Find text between ** markers (markdown bold)
|
||||
pattern = r'\*\*([^*]+)\*\*'
|
||||
matches = re.findall(pattern, text)
|
||||
# Filter out non-company text (too short, common words)
|
||||
return [m for m in matches if len(m) > 2 and not m.lower() in ['kontakt', 'tel', 'www', 'email']]
|
||||
|
||||
def format_for_prompt(self, context: Optional[Dict] = None) -> str:
|
||||
"""
|
||||
Format learning context as text for inclusion in AI prompt.
|
||||
|
||||
Args:
|
||||
context: Learning context (fetches if not provided)
|
||||
|
||||
Returns:
|
||||
Formatted string for prompt injection
|
||||
"""
|
||||
if context is None:
|
||||
context = self.get_learning_context()
|
||||
|
||||
lines = []
|
||||
|
||||
# Add positive examples section
|
||||
if context['positive_examples']:
|
||||
lines.append("\n📚 PRZYKŁADY DOBRYCH ODPOWIEDZI (ucz się z nich):")
|
||||
for i, ex in enumerate(context['positive_examples'][:3], 1):
|
||||
lines.append(f"\nPrzykład {i}:")
|
||||
lines.append(f"Pytanie: {ex.query}")
|
||||
lines.append(f"Odpowiedź: {ex.response[:300]}{'...' if len(ex.response) > 300 else ''}")
|
||||
|
||||
# Add negative patterns to avoid
|
||||
if context['negative_patterns']:
|
||||
lines.append("\n\n⚠️ UNIKAJ takich odpowiedzi:")
|
||||
for pattern in context['negative_patterns']:
|
||||
lines.append(f"- {pattern}")
|
||||
|
||||
# Add guidance based on negative feedback
|
||||
if context['negative_examples']:
|
||||
lines.append("\n\n❌ Użytkownicy ocenili negatywnie odpowiedzi typu:")
|
||||
for ex in context['negative_examples'][:2]:
|
||||
if ex.feedback_comment:
|
||||
lines.append(f"- Pytanie: '{ex.query[:50]}...' - komentarz: {ex.feedback_comment}")
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
def invalidate_cache(self):
|
||||
"""Force cache refresh on next request"""
|
||||
self._cache = None
|
||||
self._cache_time = None
|
||||
|
||||
def record_feedback_used(self, message_id: int, examples_used: List[int]):
|
||||
"""
|
||||
Record which feedback examples were used for a response.
|
||||
|
||||
This helps track the effectiveness of few-shot learning.
|
||||
|
||||
Args:
|
||||
message_id: The AI response message ID
|
||||
examples_used: List of message IDs that were used as examples
|
||||
"""
|
||||
# TODO: Implement tracking in separate table for analytics
|
||||
logger.info(f"Feedback examples {examples_used} used for message {message_id}")
|
||||
|
||||
|
||||
# Global service instance
|
||||
_feedback_service: Optional[FeedbackLearningService] = None
|
||||
|
||||
|
||||
def get_feedback_learning_service() -> FeedbackLearningService:
|
||||
"""Get or create global FeedbackLearningService instance"""
|
||||
global _feedback_service
|
||||
if _feedback_service is None:
|
||||
_feedback_service = FeedbackLearningService()
|
||||
return _feedback_service
|
||||
@ -39,6 +39,13 @@ from database import (
|
||||
AIChatMessage
|
||||
)
|
||||
|
||||
# Import feedback learning service for few-shot learning
|
||||
try:
|
||||
from feedback_learning_service import get_feedback_learning_service
|
||||
FEEDBACK_LEARNING_AVAILABLE = True
|
||||
except ImportError:
|
||||
FEEDBACK_LEARNING_AVAILABLE = False
|
||||
|
||||
|
||||
class NordaBizChatEngine:
|
||||
"""
|
||||
@ -452,6 +459,18 @@ class NordaBizChatEngine:
|
||||
- Odpowiadaj PO POLSKU
|
||||
"""
|
||||
|
||||
# Add feedback-based learning context (few-shot examples)
|
||||
if FEEDBACK_LEARNING_AVAILABLE:
|
||||
try:
|
||||
feedback_service = get_feedback_learning_service()
|
||||
learning_context = feedback_service.format_for_prompt()
|
||||
if learning_context:
|
||||
system_prompt += learning_context
|
||||
except Exception as e:
|
||||
# Don't fail if feedback learning has issues
|
||||
import logging
|
||||
logging.getLogger(__name__).warning(f"Feedback learning error: {e}")
|
||||
|
||||
# Add ALL companies in compact JSON format
|
||||
if context.get('all_companies'):
|
||||
system_prompt += "\n\n🏢 PEŁNA BAZA FIRM (wybierz najlepsze):\n"
|
||||
|
||||
@ -64,7 +64,10 @@ GRANT ALL ON TABLE company_website_analysis TO nordabiz_app;
|
||||
|
||||
def run_migration():
|
||||
print(f"Connecting to database...")
|
||||
print(f"URL: {DATABASE_URL.replace('NordaBiz2025Secure', '****')}")
|
||||
# Mask password in output
|
||||
import re
|
||||
masked_url = re.sub(r':([^:@]+)@', ':****@', DATABASE_URL)
|
||||
print(f"URL: {masked_url}")
|
||||
|
||||
try:
|
||||
conn = psycopg2.connect(DATABASE_URL)
|
||||
|
||||
@ -217,7 +217,142 @@
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-muted">Brak ocen - poproś użytkowników o feedback!</p>
|
||||
<p class="text-muted">Brak ocen - popros uzytkownikow o feedback!</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- AI Learning Status Section -->
|
||||
<div class="section">
|
||||
<h2>Uczenie AI z feedbacku</h2>
|
||||
<div id="learningStatus">
|
||||
<p class="text-muted">Ladowanie statusu uczenia...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.learning-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
.learning-card {
|
||||
background: var(--background);
|
||||
padding: var(--spacing-lg);
|
||||
border-radius: var(--radius);
|
||||
text-align: center;
|
||||
}
|
||||
.learning-card.active {
|
||||
border-left: 4px solid var(--success);
|
||||
}
|
||||
.learning-card.seed {
|
||||
border-left: 4px solid var(--warning);
|
||||
}
|
||||
.learning-value {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: 700;
|
||||
color: var(--primary);
|
||||
}
|
||||
.learning-label {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.example-card {
|
||||
background: var(--background);
|
||||
padding: var(--spacing-md);
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: var(--spacing-md);
|
||||
border-left: 3px solid var(--success);
|
||||
}
|
||||
.example-card.negative {
|
||||
border-left-color: var(--error);
|
||||
}
|
||||
.example-query {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
.example-response {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
// Load AI Learning Status
|
||||
async function loadLearningStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/admin/ai-learning-status');
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
document.getElementById('learningStatus').innerHTML =
|
||||
'<p class="text-muted">Blad ladowania statusu</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.learning_active) {
|
||||
document.getElementById('learningStatus').innerHTML =
|
||||
'<p class="text-muted">Uczenie z feedbacku nieaktywne</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
const stats = data.stats || {};
|
||||
const usingSeed = data.using_seed_examples;
|
||||
|
||||
let html = `
|
||||
<div class="learning-grid">
|
||||
<div class="learning-card ${usingSeed ? 'seed' : 'active'}">
|
||||
<div class="learning-value">${usingSeed ? 'Seed' : 'Aktywne'}</div>
|
||||
<div class="learning-label">${usingSeed ? 'Uzywa przykladow startowych' : 'Uczy sie z feedbacku'}</div>
|
||||
</div>
|
||||
<div class="learning-card">
|
||||
<div class="learning-value">${data.positive_examples_count}</div>
|
||||
<div class="learning-label">Pozytywnych przykladow</div>
|
||||
</div>
|
||||
<div class="learning-card">
|
||||
<div class="learning-value">${stats.feedback_rate || 0}%</div>
|
||||
<div class="learning-label">Wskaznik feedbacku</div>
|
||||
</div>
|
||||
<div class="learning-card">
|
||||
<div class="learning-value">${stats.positive_rate || 0}%</div>
|
||||
<div class="learning-label">Pozytywnych ocen</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Show positive examples
|
||||
if (data.positive_examples && data.positive_examples.length > 0) {
|
||||
html += '<h3 style="margin: var(--spacing-lg) 0 var(--spacing-md);">Przyklady uzywane do nauki</h3>';
|
||||
for (const ex of data.positive_examples.slice(0, 3)) {
|
||||
html += `
|
||||
<div class="example-card">
|
||||
<div class="example-query">Q: ${ex.query}</div>
|
||||
<div class="example-response">${ex.response}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Show patterns to avoid
|
||||
if (data.negative_patterns && data.negative_patterns.length > 0) {
|
||||
html += '<h3 style="margin: var(--spacing-lg) 0 var(--spacing-md);">Wzorce do unikania</h3>';
|
||||
html += '<ul style="color: var(--error); font-size: var(--font-size-sm);">';
|
||||
for (const pattern of data.negative_patterns) {
|
||||
html += `<li>${pattern}</li>`;
|
||||
}
|
||||
html += '</ul>';
|
||||
}
|
||||
|
||||
document.getElementById('learningStatus').innerHTML = html;
|
||||
} catch (error) {
|
||||
console.error('Error loading learning status:', error);
|
||||
document.getElementById('learningStatus').innerHTML =
|
||||
'<p class="text-muted">Blad ladowania statusu</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// Load on page load
|
||||
loadLearningStatus();
|
||||
{% endblock %}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user