- Invoer van een 'constanten' cache op niveau van useTranslation.js, om in de ProgressTracker de boodschappen in de juiste taal te zetten.
326 lines
9.3 KiB
Vue
326 lines
9.3 KiB
Vue
<template>
|
|
<div class="message-history-container">
|
|
<!-- Messages container -->
|
|
<div class="chat-messages" ref="messagesContainer">
|
|
<!-- Loading indicator for load more -->
|
|
<div v-if="$slots.loading" class="load-more-indicator">
|
|
<slot name="loading"></slot>
|
|
</div>
|
|
|
|
<!-- Empty state -->
|
|
<div v-if="messages.length === 0" class="empty-state">
|
|
<div class="empty-icon">💬</div>
|
|
<div class="empty-text">Nog geen berichten</div>
|
|
<div class="empty-subtext">Start een gesprek door een bericht te typen!</div>
|
|
</div>
|
|
|
|
<!-- Message list -->
|
|
<template v-else>
|
|
<!-- Messages -->
|
|
<template v-for="(message, index) in messages" :key="message.id">
|
|
<!-- The actual message -->
|
|
<chat-message
|
|
:message="message"
|
|
:is-submitting-form="isSubmittingForm"
|
|
:api-prefix="apiPrefix"
|
|
@image-loaded="handleImageLoaded"
|
|
@specialist-complete="$emit('specialist-complete', $event)"
|
|
@specialist-error="$emit('specialist-error', $event)"
|
|
></chat-message>
|
|
</template>
|
|
</template>
|
|
|
|
<!-- Typing indicator -->
|
|
<typing-indicator v-if="isTyping"></typing-indicator>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import ChatMessage from './ChatMessage.vue';
|
|
import TypingIndicator from './TypingIndicator.vue';
|
|
import { useTranslationClient } from '../js/composables/useTranslation.js';
|
|
|
|
export default {
|
|
name: 'MessageHistory',
|
|
components: {
|
|
'chat-message': ChatMessage,
|
|
'typing-indicator': TypingIndicator
|
|
},
|
|
setup() {
|
|
const { translateSafe } = useTranslationClient();
|
|
|
|
return {
|
|
translateSafe
|
|
};
|
|
},
|
|
props: {
|
|
messages: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
isTyping: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
isSubmittingForm: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
apiPrefix: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
autoScroll: {
|
|
type: Boolean,
|
|
default: true
|
|
}
|
|
},
|
|
emits: ['load-more', 'specialist-complete', 'specialist-error'],
|
|
data() {
|
|
return {
|
|
isAtBottom: true,
|
|
showScrollButton: false,
|
|
unreadCount: 0,
|
|
languageChangeHandler: null
|
|
};
|
|
},
|
|
watch: {
|
|
messages: {
|
|
handler(newMessages, oldMessages) {
|
|
// Auto-scroll when new messages are added
|
|
if (this.autoScroll && newMessages.length > (oldMessages?.length || 0)) {
|
|
this.$nextTick(() => {
|
|
this.scrollToBottom();
|
|
});
|
|
}
|
|
},
|
|
deep: true
|
|
},
|
|
isTyping(newVal) {
|
|
if (newVal && this.autoScroll) {
|
|
this.$nextTick(() => {
|
|
this.scrollToBottom();
|
|
});
|
|
}
|
|
}
|
|
},
|
|
created() {
|
|
// Maak een benoemde handler voor betere cleanup
|
|
this.languageChangeHandler = (event) => {
|
|
if (event.detail && event.detail.language) {
|
|
this.handleLanguageChange(event);
|
|
}
|
|
};
|
|
|
|
// Luister naar taalwijzigingen
|
|
document.addEventListener('language-changed', this.languageChangeHandler);
|
|
},
|
|
mounted() {
|
|
this.setupScrollListener();
|
|
|
|
// Initial scroll to bottom
|
|
if (this.autoScroll) {
|
|
this.$nextTick(() => {
|
|
this.scrollToBottom();
|
|
});
|
|
}
|
|
},
|
|
beforeUnmount() {
|
|
// Cleanup scroll listener
|
|
const container = this.$refs.messagesContainer;
|
|
if (container) {
|
|
container.removeEventListener('scroll', this.handleScroll);
|
|
}
|
|
|
|
// Cleanup language change listener
|
|
if (this.languageChangeHandler) {
|
|
document.removeEventListener('language-changed', this.languageChangeHandler);
|
|
}
|
|
},
|
|
methods: {
|
|
async handleLanguageChange(event) {
|
|
// Controleer of dit het eerste bericht is in een gesprek met maar één bericht
|
|
if (this.messages.length === 1 && this.messages[0].sender === 'ai') {
|
|
const firstMessage = this.messages[0];
|
|
|
|
// Controleer of we een originele inhoud hebben om te vertalen
|
|
if (firstMessage.content) {
|
|
|
|
if (!firstMessage.originalContent) {
|
|
firstMessage.originalContent = firstMessage.content;
|
|
}
|
|
|
|
console.log('Originele inhoud van eerste AI bericht:', firstMessage.originalContent);
|
|
console.log('Content eerste AI bericht:', firstMessage.content);
|
|
|
|
console.log('Vertaling van eerste AI bericht naar:', event.detail.language);
|
|
|
|
try {
|
|
// Gebruik moderne translateSafe composable
|
|
const translatedText = await this.translateSafe(
|
|
firstMessage.originalContent,
|
|
event.detail.language,
|
|
{
|
|
context: 'chat_message',
|
|
apiPrefix: this.apiPrefix,
|
|
fallbackText: firstMessage.originalContent
|
|
}
|
|
);
|
|
|
|
// Update de inhoud van het bericht
|
|
firstMessage.content = translatedText;
|
|
console.log('Eerste bericht succesvol vertaald');
|
|
} catch (error) {
|
|
console.error('Fout bij vertalen eerste bericht:', error);
|
|
// Fallback naar originele inhoud
|
|
firstMessage.content = firstMessage.originalContent;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
scrollToBottom() {
|
|
const container = this.$refs.messagesContainer;
|
|
if (container) {
|
|
container.scrollTop = container.scrollHeight;
|
|
this.isAtBottom = true;
|
|
this.showScrollButton = false;
|
|
this.unreadCount = 0;
|
|
}
|
|
},
|
|
|
|
setupScrollListener() {
|
|
const container = this.$refs.messagesContainer;
|
|
if (!container) return;
|
|
|
|
container.addEventListener('scroll', this.handleScroll);
|
|
},
|
|
|
|
handleScroll() {
|
|
const container = this.$refs.messagesContainer;
|
|
if (!container) return;
|
|
|
|
const threshold = 100; // pixels from bottom
|
|
const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < threshold;
|
|
|
|
this.isAtBottom = isNearBottom;
|
|
|
|
// Load more messages when scrolled to top
|
|
if (container.scrollTop === 0) {
|
|
this.$emit('load-more');
|
|
}
|
|
},
|
|
|
|
handleImageLoaded() {
|
|
// Auto-scroll when images load to maintain position
|
|
if (this.isAtBottom) {
|
|
this.$nextTick(() => this.scrollToBottom());
|
|
}
|
|
},
|
|
|
|
searchMessages(query) {
|
|
// Simple message search
|
|
if (!query.trim()) return this.messages;
|
|
|
|
const searchTerm = query.toLowerCase();
|
|
return this.messages.filter(message =>
|
|
message.content &&
|
|
message.content.toLowerCase().includes(searchTerm)
|
|
);
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.message-history-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.chat-messages {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 10px;
|
|
scroll-behavior: smooth;
|
|
}
|
|
|
|
.load-more-indicator {
|
|
text-align: center;
|
|
padding: 10px;
|
|
color: #666;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.empty-state {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 100%;
|
|
text-align: center;
|
|
color: #666;
|
|
padding: 40px 20px;
|
|
}
|
|
|
|
.empty-icon {
|
|
font-size: 3rem;
|
|
margin-bottom: 16px;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.empty-text {
|
|
font-size: 1.2rem;
|
|
font-weight: 500;
|
|
margin-bottom: 8px;
|
|
color: #333;
|
|
}
|
|
|
|
.empty-subtext {
|
|
font-size: 0.9rem;
|
|
color: #666;
|
|
max-width: 300px;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
/* Scrollbar styling */
|
|
.chat-messages::-webkit-scrollbar {
|
|
width: 6px;
|
|
}
|
|
|
|
.chat-messages::-webkit-scrollbar-track {
|
|
background: #f1f1f1;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.chat-messages::-webkit-scrollbar-thumb {
|
|
background: #c1c1c1;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.chat-messages::-webkit-scrollbar-thumb:hover {
|
|
background: #a8a8a8;
|
|
}
|
|
|
|
/* Responsive adjustments */
|
|
@media (max-width: 768px) {
|
|
.chat-messages {
|
|
padding: 8px;
|
|
}
|
|
|
|
.empty-state {
|
|
padding: 20px 16px;
|
|
}
|
|
|
|
.empty-icon {
|
|
font-size: 2.5rem;
|
|
}
|
|
|
|
.empty-text {
|
|
font-size: 1.1rem;
|
|
}
|
|
}
|
|
</style> |