- Invoer van een 'constanten' cache op niveau van useTranslation.js, om in de ProgressTracker de boodschappen in de juiste taal te zetten.
583 lines
18 KiB
Vue
583 lines
18 KiB
Vue
<template>
|
|
<div class="progress-tracker" :class="{
|
|
'expanded': isExpanded,
|
|
'completed': isCompleted && !hasError,
|
|
'error': error || hasError
|
|
}">
|
|
<div
|
|
class="progress-header"
|
|
@click="toggleExpand"
|
|
:title="isExpanded ? 'Inklappen' : 'Uitklappen voor volledige voortgang'"
|
|
>
|
|
<div class="progress-title">
|
|
<!-- Evie working animatie tijdens processing -->
|
|
<img v-if="isProcessing"
|
|
src="/static/assets/img/evie_working.webp"
|
|
alt="Bezig met verwerken..."
|
|
class="progress-icon working-animation">
|
|
|
|
<!-- Status icons na completion -->
|
|
<span v-else-if="connecting" class="spinner progress-icon"></span>
|
|
<span v-else-if="error" class="status-icon error progress-icon">✗</span>
|
|
<span v-else-if="isCompleted" class="status-icon completed progress-icon">✓</span>
|
|
|
|
<!-- Status tekst -->
|
|
<span v-if="error">{{ errorText }}</span>
|
|
<span v-else-if="isCompleted">{{ completedText }}</span>
|
|
<span v-else>{{ processingText }}</span>
|
|
</div>
|
|
<div class="progress-toggle">
|
|
{{ isExpanded ? '▼' : '◄' }}
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="error" class="progress-error">
|
|
{{ error }}
|
|
</div>
|
|
|
|
<!-- Content alleen tonen als expanded -->
|
|
<div
|
|
v-if="isExpanded"
|
|
ref="progressContainer"
|
|
class="progress-content"
|
|
>
|
|
<div
|
|
v-for="(line, index) in progressLines"
|
|
:key="index"
|
|
class="progress-line"
|
|
>
|
|
{{ line }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { useTranslationClient, useConstantsTranslation } from '../js/composables/useTranslation.js';
|
|
|
|
export default {
|
|
name: 'ProgressTracker',
|
|
props: {
|
|
taskId: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
apiPrefix: {
|
|
type: String,
|
|
default: ''
|
|
}
|
|
},
|
|
emits: ['specialist-complete', 'specialist-error'],
|
|
data() {
|
|
return {
|
|
isExpanded: false,
|
|
isCompleted: false,
|
|
hasError: false,
|
|
connecting: true,
|
|
error: null,
|
|
progressLines: [],
|
|
eventSource: null,
|
|
// Vertaalde status teksten
|
|
translatedStatusTexts: {
|
|
error: 'Error while processing',
|
|
completed: 'Processing completed',
|
|
processing: 'Processing...'
|
|
},
|
|
currentLanguage: 'nl'
|
|
};
|
|
},
|
|
computed: {
|
|
isProcessing() {
|
|
return !this.isCompleted && !this.hasError && !this.connecting;
|
|
},
|
|
// Computed properties voor vertaalde status teksten
|
|
errorText() {
|
|
return this.translatedStatusTexts.error;
|
|
},
|
|
completedText() {
|
|
return this.translatedStatusTexts.completed;
|
|
},
|
|
processingText() {
|
|
return this.translatedStatusTexts.processing;
|
|
}
|
|
},
|
|
mounted() {
|
|
this.connectToProgressStream();
|
|
|
|
// Setup translation composables
|
|
const { translateSafe } = useTranslationClient();
|
|
const { translateConstants, getCachedTranslations, getCachedLanguage } = useConstantsTranslation();
|
|
this.translateSafe = translateSafe;
|
|
this.translateConstants = translateConstants;
|
|
this.getCachedTranslations = getCachedTranslations;
|
|
this.getCachedLanguage = getCachedLanguage;
|
|
|
|
// Check if we already have cached translations and apply them
|
|
const cachedTranslations = this.getCachedTranslations();
|
|
if (cachedTranslations) {
|
|
console.log('ProgressTracker: Applying cached translations on mount');
|
|
this.translatedStatusTexts = { ...cachedTranslations };
|
|
this.currentLanguage = this.getCachedLanguage();
|
|
}
|
|
|
|
// Luister naar taalwijzigingen
|
|
this.languageChangeHandler = (event) => {
|
|
if (event.detail && event.detail.language) {
|
|
this.handleLanguageChange(event.detail.language);
|
|
}
|
|
};
|
|
document.addEventListener('language-changed', this.languageChangeHandler);
|
|
},
|
|
beforeUnmount() {
|
|
this.disconnectEventSource();
|
|
|
|
// Cleanup language change listener
|
|
if (this.languageChangeHandler) {
|
|
document.removeEventListener('language-changed', this.languageChangeHandler);
|
|
}
|
|
},
|
|
methods: {
|
|
async handleLanguageChange(newLanguage) {
|
|
console.log('ProgressTracker: Language change to', newLanguage);
|
|
|
|
// Skip if same language
|
|
if (this.currentLanguage === newLanguage) {
|
|
return;
|
|
}
|
|
|
|
this.currentLanguage = newLanguage;
|
|
|
|
// Define the original Dutch constants
|
|
const originalTexts = {
|
|
error: 'Fout bij verwerking',
|
|
completed: 'Verwerking voltooid',
|
|
processing: 'Bezig met redeneren...'
|
|
};
|
|
|
|
try {
|
|
// Use global constants translation with caching
|
|
const translatedTexts = await this.translateConstants(originalTexts, newLanguage, {
|
|
context: 'progress_tracker',
|
|
apiPrefix: this.apiPrefix
|
|
});
|
|
|
|
// Update component state with translated texts
|
|
this.translatedStatusTexts = translatedTexts;
|
|
|
|
console.log('ProgressTracker: Successfully updated status texts for', newLanguage);
|
|
} catch (error) {
|
|
console.error('ProgressTracker: Error translating status texts:', error);
|
|
// Fallback to original Dutch texts
|
|
this.translatedStatusTexts = originalTexts;
|
|
}
|
|
},
|
|
|
|
connectToProgressStream() {
|
|
if (!this.taskId) {
|
|
console.error('Geen task ID beschikbaar voor progress tracking');
|
|
return;
|
|
}
|
|
|
|
console.log('Connecting to progress stream for task:', this.taskId);
|
|
|
|
// Construct the SSE URL
|
|
const baseUrl = window.location.origin;
|
|
const sseUrl = `${baseUrl}${this.apiPrefix}/api/task_progress/${this.taskId}`;
|
|
|
|
console.log('SSE URL:', sseUrl);
|
|
|
|
try {
|
|
this.eventSource = new EventSource(sseUrl);
|
|
|
|
this.eventSource.onopen = () => {
|
|
console.log('Progress stream connected');
|
|
this.connecting = false;
|
|
};
|
|
|
|
this.eventSource.onmessage = (event) => {
|
|
this.handleProgressUpdate(event);
|
|
};
|
|
|
|
this.eventSource.onerror = (event) => {
|
|
console.error('Progress stream error:', event);
|
|
this.handleError(event);
|
|
};
|
|
|
|
} catch (error) {
|
|
console.error('Failed to create EventSource:', error);
|
|
this.error = 'Kan geen verbinding maken met de voortgangsstream.';
|
|
this.connecting = false;
|
|
}
|
|
},
|
|
|
|
disconnectEventSource() {
|
|
if (this.eventSource) {
|
|
console.log('Disconnecting progress stream');
|
|
this.eventSource.close();
|
|
this.eventSource = null;
|
|
}
|
|
},
|
|
|
|
handleProgressUpdate(event) {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
console.log('Progress update:', data);
|
|
|
|
// Check voor processing_type om te bepalen welke handler te gebruiken
|
|
if (data.processing_type === 'EveAI Specialist Complete') {
|
|
console.log('Detected specialist complete via processing_type');
|
|
this.handleSpecialistComplete(event);
|
|
return;
|
|
}
|
|
|
|
// Check voor andere completion statuses en errors
|
|
if (data.processing_type === 'EveAI Specialist Error')
|
|
{
|
|
console.log('Detected specialist error via processing_type or status');
|
|
this.handleSpecialistError(event);
|
|
return;
|
|
}
|
|
|
|
// Voeg bericht toe aan progressLines
|
|
if (data.message) {
|
|
this.progressLines.push(data.message);
|
|
} else if (data.data && data.data.message) {
|
|
this.progressLines.push(data.data.message);
|
|
} else if (data.processing_type) {
|
|
// Gebruik processing_type als message wanneer er geen andere message is
|
|
this.progressLines.push(`${data.processing_type}...`);
|
|
}
|
|
|
|
// Auto-scroll to bottom if expanded
|
|
if (this.isExpanded) {
|
|
this.$nextTick(() => {
|
|
const container = this.$refs.progressContainer;
|
|
if (container) {
|
|
container.scrollTop = container.scrollHeight;
|
|
}
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Error parsing progress data:', error);
|
|
}
|
|
},
|
|
|
|
handleSpecialistComplete(event) {
|
|
console.log('Specialist complete event:', event);
|
|
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
console.log('Specialist complete data:', data);
|
|
|
|
this.isCompleted = true;
|
|
this.connecting = false;
|
|
this.disconnectEventSource();
|
|
|
|
// Verschillende manieren om de result data te verkrijgen
|
|
let resultData = null;
|
|
let answer = null;
|
|
let formRequest = null;
|
|
let interactionId = null;
|
|
|
|
resultData = data.data.result
|
|
console.log('Result data:', resultData);
|
|
|
|
if (resultData) {
|
|
// Standaard format
|
|
answer = resultData.answer;
|
|
formRequest = resultData.form_request;
|
|
interactionId = data.data.interaction_id;
|
|
}
|
|
this.$emit('specialist-complete', {
|
|
answer: answer,
|
|
form_request: formRequest,
|
|
result: resultData,
|
|
interactionId: interactionId,
|
|
taskId: this.taskId
|
|
})
|
|
} catch (error) {
|
|
console.error('Error parsing specialist complete data:', error);
|
|
this.handleSpecialistError({
|
|
data: JSON.stringify({
|
|
Error: 'Failed to parse completion data',
|
|
processing_type: 'EveAI Specialist Error'
|
|
})
|
|
});
|
|
}
|
|
},
|
|
|
|
handleSpecialistError(event) {
|
|
console.log('Specialist error event:', event);
|
|
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
console.log('Specialist error data:', data);
|
|
|
|
this.isCompleted = true;
|
|
this.hasError = true;
|
|
this.connecting = false;
|
|
this.disconnectEventSource();
|
|
|
|
// Set user-friendly error message
|
|
const errorMessage = "We could not process your request. Please try again later.";
|
|
this.error = errorMessage;
|
|
|
|
// Extract error details from various possible locations
|
|
const originalError =
|
|
data.Error ||
|
|
data.error ||
|
|
data.message ||
|
|
data.data?.error ||
|
|
data.data?.Error ||
|
|
data.data?.message ||
|
|
'Unknown error';
|
|
|
|
// Log the actual error for debug purposes
|
|
console.error('Specialist Error:', originalError);
|
|
|
|
// Emit error event to parent
|
|
this.$emit('specialist-error', {
|
|
message: errorMessage,
|
|
originalError: originalError,
|
|
taskId: this.taskId
|
|
});
|
|
} catch (error) {
|
|
console.error('Error parsing specialist error data:', error);
|
|
this.error = 'Er is een onbekende fout opgetreden.';
|
|
this.isCompleted = true;
|
|
this.hasError = true;
|
|
this.connecting = false;
|
|
this.disconnectEventSource();
|
|
|
|
// Emit generic error
|
|
this.$emit('specialist-error', {
|
|
message: 'Er is een onbekende fout opgetreden.',
|
|
originalError: 'Failed to parse error data',
|
|
taskId: this.taskId
|
|
});
|
|
}
|
|
},
|
|
|
|
handleError(event) {
|
|
console.error('SSE Error event:', event);
|
|
this.error = 'Er is een fout opgetreden bij het verwerken van updates.';
|
|
this.connecting = false;
|
|
|
|
// Try to parse error data
|
|
try {
|
|
if (event.data) {
|
|
const errorData = JSON.parse(event.data);
|
|
if (errorData && errorData.message) {
|
|
this.error = errorData.message;
|
|
}
|
|
}
|
|
} catch (err) {
|
|
// Keep generic error message if parsing fails
|
|
}
|
|
|
|
// Emit error to parent
|
|
this.$emit('specialist-error', {
|
|
message: this.error,
|
|
originalError: 'SSE Connection Error',
|
|
taskId: this.taskId
|
|
});
|
|
},
|
|
|
|
toggleExpand() {
|
|
this.isExpanded = !this.isExpanded;
|
|
|
|
if (this.isExpanded) {
|
|
this.$nextTick(() => {
|
|
const container = this.$refs.progressContainer;
|
|
if (container) {
|
|
container.scrollTop = container.scrollHeight;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.progress-tracker {
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 8px;
|
|
margin: 10px 0;
|
|
background-color: #f9f9f9;
|
|
font-family: Arial, sans-serif;
|
|
font-size: 14px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.progress-tracker.expanded {
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.progress-tracker.completed {
|
|
border-color: #4caf50;
|
|
background-color: #f1f8e9;
|
|
}
|
|
|
|
.progress-tracker.error {
|
|
border-color: #f44336;
|
|
background-color: #ffebee;
|
|
}
|
|
|
|
.progress-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 15px;
|
|
cursor: pointer;
|
|
border-bottom: 1px solid #e0e0e0;
|
|
background-color: #fff;
|
|
border-radius: 8px 8px 0 0;
|
|
transition: background-color 0.2s ease;
|
|
}
|
|
|
|
.progress-header:hover {
|
|
background-color: #f5f5f5;
|
|
}
|
|
|
|
.progress-title {
|
|
display: flex;
|
|
align-items: center;
|
|
font-weight: 500;
|
|
color: #333;
|
|
}
|
|
|
|
.status-icon {
|
|
margin-right: 8px;
|
|
font-weight: bold;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.status-icon.completed {
|
|
color: #4caf50;
|
|
}
|
|
|
|
.status-icon.error {
|
|
color: #f44336;
|
|
}
|
|
|
|
.status-icon.in-progress {
|
|
color: #2196f3;
|
|
}
|
|
|
|
.spinner {
|
|
display: inline-block;
|
|
width: 16px;
|
|
height: 16px;
|
|
border: 2px solid #f3f3f3;
|
|
border-top: 2px solid #2196f3;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
|
|
.progress-toggle {
|
|
color: #666;
|
|
font-size: 12px;
|
|
transition: transform 0.2s ease;
|
|
}
|
|
|
|
.progress-tracker.expanded .progress-toggle {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
.progress-error {
|
|
padding: 10px 15px;
|
|
background-color: #ffcdd2;
|
|
color: #c62828;
|
|
border-top: 1px solid #e0e0e0;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.progress-content {
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
padding: 10px 15px;
|
|
background-color: #fff;
|
|
border-radius: 0 0 8px 8px;
|
|
transition: max-height 0.3s ease;
|
|
}
|
|
|
|
.progress-content.single-line {
|
|
max-height: 40px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.progress-line {
|
|
padding: 2px 0;
|
|
color: #555;
|
|
font-size: 13px;
|
|
line-height: 1.4;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.progress-line:last-child {
|
|
font-weight: 500;
|
|
color: #333;
|
|
}
|
|
|
|
/* Scrollbar styling */
|
|
.progress-content::-webkit-scrollbar {
|
|
width: 4px;
|
|
}
|
|
|
|
.progress-content::-webkit-scrollbar-track {
|
|
background: #f1f1f1;
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.progress-content::-webkit-scrollbar-thumb {
|
|
background: #c1c1c1;
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.progress-content::-webkit-scrollbar-thumb:hover {
|
|
background: #a8a8a8;
|
|
}
|
|
|
|
/* Responsive adjustments */
|
|
@media (max-width: 768px) {
|
|
.progress-header {
|
|
padding: 10px 12px;
|
|
}
|
|
|
|
.progress-content {
|
|
padding: 8px 12px;
|
|
max-height: 150px;
|
|
}
|
|
|
|
.progress-content.single-line {
|
|
max-height: 35px;
|
|
}
|
|
|
|
.progress-line {
|
|
font-size: 12px;
|
|
}
|
|
}
|
|
|
|
/* Animation for new progress lines */
|
|
.progress-line {
|
|
animation: fadeIn 0.3s ease-in;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-5px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
</style> |