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 - 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 - Uwzględniaj kontekst firmy użytkownika w odpowiedziach
- NIE ujawniaj danych technicznych (user_id, company_id, rola systemowa) - 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 # Inject user memory (facts + conversation summaries) into prompt

View File

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