feat(nordagpt): avatars in chat + anti-hallucination rule for companies
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

- NordaGPT icon (nordagpt-icon.svg) as AI avatar instead of "AI" text
- User profile photo as avatar (falls back to initial letter)
- CRITICAL: added strict rule to never hallucinate company names
- Only mention companies that exist in the provided database

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-28 06:12:49 +01:00
parent cc78711e17
commit 0a7fe6389f
2 changed files with 33 additions and 7 deletions

View File

@ -1008,6 +1008,13 @@ ZASADY PERSONALIZACJI:
- Na pytania "co wiesz o mnie?" / "kim jestem?" wypisz powyższe dane + powiązania firmowe z bazy
- Uwzględniaj kontekst firmy użytkownika w odpowiedziach
- NIE ujawniaj danych technicznych (user_id, company_id, rola systemowa)
KRYTYCZNA ZASADA DOTYCZĄCA FIRM:
- WYMIENIAJ WYŁĄCZNIE firmy, które znajdują się w dostarczonej bazie danych poniżej
- NIGDY nie wymyślaj, nie zgaduj ani nie halucynuj nazw firm
- Jeśli w bazie nie ma firmy pasującej do zapytania, powiedz wprost: "W bazie Izby nie znalazłem firmy o takim profilu"
- Każda wymieniona firma MUSI mieć link do profilu w formacie [Nazwa](/firma/slug)
- Jeśli nie masz pewności czy firma istnieje w bazie NIE WYMIENIAJ JEJ
"""
# Inject user memory (facts + conversation summaries) into prompt

View File

@ -433,9 +433,22 @@
color: white;
}
.message.assistant .message-avatar img {
width: 24px;
height: 24px;
filter: brightness(0) invert(1);
}
.message.user .message-avatar {
background: var(--primary);
color: white;
overflow: hidden;
}
.message.user .message-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.message-content {
@ -1828,7 +1841,7 @@
<!-- Typing indicator -->
<div class="message assistant" id="typingIndicator" style="display: none;">
<div class="message-avatar">AI</div>
<div class="message-avatar"><img src="{{ url_for('static', filename='img/nordagpt-icon.svg') }}" alt="AI"></div>
<div class="typing-indicator active">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
@ -1861,6 +1874,12 @@
{% block extra_js %}
// NordaGPT Chat - State
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '';
const AI_AVATAR_HTML = '<img src="{{ url_for("static", filename="img/nordagpt-icon.svg") }}" alt="AI">';
{% if current_user and current_user.avatar_path %}
const USER_AVATAR_HTML = '<img src="{{ url_for("static", filename=current_user.avatar_path) }}" alt="{{ current_user.name[:1] }}">';
{% else %}
const USER_AVATAR_HTML = '{{ current_user.name[:1].upper() if current_user else "U" }}';
{% endif %}
let currentConversationId = null;
let conversations = [];
let currentModel = 'flash'; // Default model (Gemini 3 Flash - thinking mode, 10K RPD)
@ -2291,7 +2310,7 @@ async function loadConversation(conversationId) {
// Clear messages and show loading
const messagesDiv = document.getElementById('chatMessages');
messagesDiv.innerHTML = '<div class="message assistant"><div class="message-avatar">AI</div><div class="message-content">Ładowanie historii...</div></div>';
messagesDiv.innerHTML = '<div class="message assistant"><div class="message-avatar"><img src="{{ url_for('static', filename='img/nordagpt-icon.svg') }}" alt="AI"></div><div class="message-content">Ładowanie historii...</div></div>';
try {
const response = await fetch(`/api/chat/${conversationId}/history`);
@ -2305,7 +2324,7 @@ async function loadConversation(conversationId) {
// Re-add typing indicator at the end
messagesDiv.innerHTML += `
<div class="message assistant" id="typingIndicator" style="display: none;">
<div class="message-avatar">AI</div>
<div class="message-avatar"><img src="{{ url_for('static', filename='img/nordagpt-icon.svg') }}" alt="AI"></div>
<div class="typing-indicator active">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
@ -2317,7 +2336,7 @@ async function loadConversation(conversationId) {
}
} catch (error) {
console.error('Error loading conversation:', error);
messagesDiv.innerHTML = '<div class="message assistant"><div class="message-avatar">AI</div><div class="message-content">Nie udało się załadować rozmowy.</div></div>';
messagesDiv.innerHTML = '<div class="message assistant"><div class="message-avatar"><img src="{{ url_for('static', filename='img/nordagpt-icon.svg') }}" alt="AI"></div><div class="message-content">Nie udało się załadować rozmowy.</div></div>';
}
// Close mobile sidebar
@ -2348,7 +2367,7 @@ function startNewConversation() {
</div>
</div>
<div class="message assistant" id="typingIndicator" style="display: none;">
<div class="message-avatar">AI</div>
<div class="message-avatar"><img src="{{ url_for('static', filename='img/nordagpt-icon.svg') }}" alt="AI"></div>
<div class="typing-indicator active">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
@ -2435,7 +2454,7 @@ async function sendMessage() {
streamMsgDiv.className = 'message assistant';
const streamAvatar = document.createElement('div');
streamAvatar.className = 'message-avatar';
streamAvatar.textContent = 'AI';
streamAvatar.innerHTML = AI_AVATAR_HTML;
const streamContent = document.createElement('div');
streamContent.className = 'message-content';
@ -2608,7 +2627,7 @@ function addMessage(role, content, animate = true, techInfo = null) {
const avatar = document.createElement('div');
avatar.className = 'message-avatar';
avatar.textContent = role === 'assistant' ? 'AI' : '{{ current_user.name[:1].upper() if current_user else "U" }}';
avatar.innerHTML = role === 'assistant' ? AI_AVATAR_HTML : USER_AVATAR_HTML;
const contentDiv = document.createElement('div');
contentDiv.className = 'message-content';