- Build of the Chat Client using Vue.js
- Accompanying css - Views to serve the Chat Client - first test version of the TRACIE_SELECTION_SPECIALIST - ESS Implemented.
This commit is contained in:
240
eveai_chat_client/static/js/components/ChatMessage.js
Normal file
240
eveai_chat_client/static/js/components/ChatMessage.js
Normal file
@@ -0,0 +1,240 @@
|
||||
export const ChatMessage = {
|
||||
name: 'ChatMessage',
|
||||
props: {
|
||||
message: {
|
||||
type: Object,
|
||||
required: true,
|
||||
validator: (message) => {
|
||||
return message.id && message.content !== undefined && message.sender && message.type;
|
||||
}
|
||||
},
|
||||
formValues: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
isSubmittingForm: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
apiPrefix: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
emits: ['submit-form', 'image-loaded', 'retry-message', 'specialist-complete', 'specialist-error'],
|
||||
data() {
|
||||
return {
|
||||
isEditing: false,
|
||||
editedContent: ''
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSpecialistError(eventData) {
|
||||
console.log('ChatMessage received specialist-error event:', eventData);
|
||||
|
||||
// Creëer een error message met correcte styling
|
||||
this.message.type = 'error';
|
||||
this.message.content = eventData.message || 'Er is een fout opgetreden bij het verwerken van uw verzoek.';
|
||||
this.message.retryable = true;
|
||||
this.message.error = true; // Voeg error flag toe voor styling
|
||||
|
||||
// Bubble up naar parent component voor verdere afhandeling
|
||||
this.$emit('specialist-error', {
|
||||
messageId: this.message.id,
|
||||
...eventData
|
||||
});
|
||||
},
|
||||
|
||||
handleSpecialistComplete(eventData) {
|
||||
console.log('ChatMessage received specialist-complete event:', eventData);
|
||||
|
||||
// Update de inhoud van het bericht met het antwoord
|
||||
if (eventData.answer) {
|
||||
console.log('Updating message content with answer:', eventData.answer);
|
||||
this.message.content = eventData.answer;
|
||||
} else {
|
||||
console.error('No answer in specialist-complete event data');
|
||||
}
|
||||
|
||||
// Bubble up naar parent component voor eventuele verdere afhandeling
|
||||
this.$emit('specialist-complete', {
|
||||
messageId: this.message.id,
|
||||
...eventData
|
||||
});
|
||||
},
|
||||
|
||||
formatMessage(content) {
|
||||
if (!content) return '';
|
||||
|
||||
// Enhanced markdown-like formatting
|
||||
return content
|
||||
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
||||
.replace(/`(.*?)`/g, '<code>$1</code>')
|
||||
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>')
|
||||
.replace(/\n/g, '<br>');
|
||||
},
|
||||
|
||||
startEdit() {
|
||||
this.editedContent = this.message.content;
|
||||
this.isEditing = true;
|
||||
},
|
||||
|
||||
saveEdit() {
|
||||
// Implementatie van bewerkingen zou hier komen
|
||||
this.message.content = this.editedContent;
|
||||
this.isEditing = false;
|
||||
},
|
||||
|
||||
cancelEdit() {
|
||||
this.isEditing = false;
|
||||
this.editedContent = '';
|
||||
},
|
||||
|
||||
submitForm() {
|
||||
this.$emit('submit-form', this.message.formData, this.message.id);
|
||||
},
|
||||
|
||||
removeMessage() {
|
||||
// Dit zou een event moeten triggeren naar de parent component
|
||||
},
|
||||
|
||||
reactToMessage(emoji) {
|
||||
// Implementatie van reacties zou hier komen
|
||||
},
|
||||
|
||||
getMessageClass() {
|
||||
if (this.message.type === 'form') {
|
||||
return 'form-message';
|
||||
}
|
||||
return `message ${this.message.sender}`;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div :class="getMessageClass()" :data-message-id="message.id">
|
||||
<!-- Normal text messages -->
|
||||
<template v-if="message.type === 'text'">
|
||||
<div class="message-content">
|
||||
<!-- Voortgangstracker voor AI berichten met task_id - NU BINNEN DE BUBBLE -->
|
||||
<progress-tracker
|
||||
v-if="message.sender === 'ai' && message.taskId"
|
||||
:task-id="message.taskId"
|
||||
:api-prefix="apiPrefix"
|
||||
class="message-progress"
|
||||
@specialist-complete="handleSpecialistComplete"
|
||||
@specialist-error="handleSpecialistError"
|
||||
></progress-tracker>
|
||||
|
||||
<!-- Edit mode -->
|
||||
<div v-if="isEditing" class="edit-mode">
|
||||
<textarea
|
||||
v-model="editedContent"
|
||||
class="edit-textarea"
|
||||
rows="3"
|
||||
@keydown.enter.ctrl="saveEdit"
|
||||
@keydown.escape="cancelEdit"
|
||||
></textarea>
|
||||
<div class="edit-actions">
|
||||
<button @click="saveEdit" class="btn-small btn-primary">Opslaan</button>
|
||||
<button @click="cancelEdit" class="btn-small btn-secondary">Annuleren</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- View mode -->
|
||||
<div v-else>
|
||||
<div
|
||||
v-html="formatMessage(message.content)"
|
||||
class="message-text"
|
||||
></div>
|
||||
<!-- Debug info -->
|
||||
<div v-if="false" class="debug-info">
|
||||
Content: {{ message.content ? message.content.length + ' chars' : 'empty' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Image messages -->
|
||||
<template v-if="message.type === 'image'">
|
||||
<div class="message-content">
|
||||
<img
|
||||
:src="message.imageUrl"
|
||||
:alt="message.alt || 'Afbeelding'"
|
||||
class="message-image"
|
||||
@load="$emit('image-loaded')"
|
||||
>
|
||||
<div v-if="message.caption" class="image-caption">
|
||||
{{ message.caption }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- File messages -->
|
||||
<template v-if="message.type === 'file'">
|
||||
<div class="message-content">
|
||||
<div class="file-attachment">
|
||||
<div class="file-icon">📎</div>
|
||||
<div class="file-info">
|
||||
<div class="file-name">{{ message.fileName }}</div>
|
||||
<div class="file-size">{{ message.fileSize }}</div>
|
||||
</div>
|
||||
<a
|
||||
:href="message.fileUrl"
|
||||
download
|
||||
class="file-download"
|
||||
title="Download"
|
||||
>
|
||||
⬇️
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Dynamic forms -->
|
||||
<template v-if="message.type === 'form'">
|
||||
<dynamic-form
|
||||
:form-data="message.formData"
|
||||
:form-values="formValues[message.id] || {}"
|
||||
:is-submitting="isSubmittingForm"
|
||||
@submit="submitForm"
|
||||
@cancel="removeMessage"
|
||||
></dynamic-form>
|
||||
</template>
|
||||
|
||||
<!-- System messages -->
|
||||
<template v-if="message.type === 'system'">
|
||||
<div class="system-message">
|
||||
<span class="system-icon">ℹ️</span>
|
||||
{{ message.content }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Error messages -->
|
||||
<template v-if="message.type === 'error'">
|
||||
<div class="error-message">
|
||||
<span class="error-icon">⚠️</span>
|
||||
{{ message.content }}
|
||||
<button
|
||||
v-if="message.retryable"
|
||||
@click="$emit('retry-message', message.id)"
|
||||
class="retry-btn"
|
||||
>
|
||||
Probeer opnieuw
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Message reactions -->
|
||||
<div v-if="message.reactions && message.reactions.length" class="message-reactions">
|
||||
<span
|
||||
v-for="reaction in message.reactions"
|
||||
:key="reaction.emoji"
|
||||
class="reaction"
|
||||
@click="reactToMessage(reaction.emoji)"
|
||||
>
|
||||
{{ reaction.emoji }} {{ reaction.count }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
Reference in New Issue
Block a user