- Layout improvements for the Chat client
This commit is contained in:
@@ -791,11 +791,6 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.working-animation {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mobile responsiveness for progress icons */
|
/* Mobile responsiveness for progress icons */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.progress-icon {
|
.progress-icon {
|
||||||
@@ -803,9 +798,4 @@
|
|||||||
height: 16px;
|
height: 16px;
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.working-animation {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -28,23 +28,6 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-logo {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-logo img {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-make-name {
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-explanation {
|
.sidebar-explanation {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|||||||
@@ -23,14 +23,6 @@
|
|||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-field label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-field input,
|
.form-field input,
|
||||||
.form-field select,
|
.form-field select,
|
||||||
.form-field textarea {
|
.form-field textarea {
|
||||||
@@ -71,11 +63,6 @@
|
|||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-text {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-description {
|
.field-description {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
|
|||||||
@@ -21,11 +21,15 @@ active_text_color<template>
|
|||||||
:allow-file-upload="true"
|
:allow-file-upload="true"
|
||||||
:allow-voice-message="false"
|
:allow-voice-message="false"
|
||||||
:form-data="currentInputFormData"
|
:form-data="currentInputFormData"
|
||||||
|
:active-ai-message="activeAiMessage"
|
||||||
|
:api-prefix="apiPrefix"
|
||||||
@send-message="sendMessage"
|
@send-message="sendMessage"
|
||||||
@update-message="updateCurrentMessage"
|
@update-message="updateCurrentMessage"
|
||||||
@upload-file="handleFileUpload"
|
@upload-file="handleFileUpload"
|
||||||
@record-voice="handleVoiceRecord"
|
@record-voice="handleVoiceRecord"
|
||||||
@submit-form="submitFormFromInput"
|
@submit-form="submitFormFromInput"
|
||||||
|
@specialist-error="handleSpecialistError"
|
||||||
|
@specialist-complete="handleSpecialistComplete"
|
||||||
ref="chatInput"
|
ref="chatInput"
|
||||||
class="chat-input-area"
|
class="chat-input-area"
|
||||||
></chat-input>
|
></chat-input>
|
||||||
@@ -150,6 +154,11 @@ export default {
|
|||||||
return this.isSearching ? this.filteredMessages : this.allMessages;
|
return this.isSearching ? this.filteredMessages : this.allMessages;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Active AI message that should be shown in ChatInput
|
||||||
|
activeAiMessage() {
|
||||||
|
return this.allMessages.find(msg => msg.isTemporarilyAtBottom);
|
||||||
|
},
|
||||||
|
|
||||||
hasMessages() {
|
hasMessages() {
|
||||||
return this.allMessages.length > 0;
|
return this.allMessages.length > 0;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="chat-input-container">
|
<div class="chat-input-container">
|
||||||
<!-- Material Icons worden nu globaal geladen in scripts.html -->
|
<!-- Material Icons worden nu globaal geladen in scripts.html -->
|
||||||
|
|
||||||
|
<!-- Active AI Message Area -->
|
||||||
|
<div v-if="activeAiMessage" class="active-ai-message-area">
|
||||||
|
<chat-message
|
||||||
|
:message="activeAiMessage"
|
||||||
|
:is-submitting-form="false"
|
||||||
|
:api-prefix="apiPrefix"
|
||||||
|
:is-latest-ai-message="true"
|
||||||
|
:is-in-input-area="true"
|
||||||
|
@image-loaded="handleImageLoaded"
|
||||||
|
@specialist-complete="$emit('specialist-complete', $event)"
|
||||||
|
@specialist-error="$emit('specialist-error', $event)"
|
||||||
|
></chat-message>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Dynamisch formulier container -->
|
<!-- Dynamisch formulier container -->
|
||||||
<div v-if="formData" class="dynamic-form-container">
|
<div v-if="formData" class="dynamic-form-container">
|
||||||
<!-- De titel wordt in DynamicForm weergegeven en niet hier om dubbele titels te voorkomen -->
|
<!-- De titel wordt in DynamicForm weergegeven en niet hier om dubbele titels te voorkomen -->
|
||||||
@@ -62,13 +77,15 @@
|
|||||||
<script>
|
<script>
|
||||||
// Importeer de benodigde componenten
|
// Importeer de benodigde componenten
|
||||||
import DynamicForm from './DynamicForm.vue';
|
import DynamicForm from './DynamicForm.vue';
|
||||||
|
import ChatMessage from './ChatMessage.vue';
|
||||||
import { useIconManager } from '../js/composables/useIconManager.js';
|
import { useIconManager } from '../js/composables/useIconManager.js';
|
||||||
import { useTranslationClient } from '../js/composables/useTranslation.js';
|
import { useTranslationClient } from '../js/composables/useTranslation.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ChatInput',
|
name: 'ChatInput',
|
||||||
components: {
|
components: {
|
||||||
'dynamic-form': DynamicForm
|
'dynamic-form': DynamicForm,
|
||||||
|
'chat-message': ChatMessage
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { watchIcon } = useIconManager();
|
const { watchIcon } = useIconManager();
|
||||||
@@ -103,8 +120,16 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
activeAiMessage: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
apiPrefix: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
},
|
},
|
||||||
emits: ['send-message', 'update-message', 'submit-form'],
|
emits: ['send-message', 'update-message', 'submit-form', 'specialist-complete', 'specialist-error'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
localMessage: this.currentMessage,
|
localMessage: this.currentMessage,
|
||||||
@@ -377,6 +402,12 @@ export default {
|
|||||||
if (JSON.stringify(newValues) !== JSON.stringify(this.formValues)) {
|
if (JSON.stringify(newValues) !== JSON.stringify(this.formValues)) {
|
||||||
this.formValues = JSON.parse(JSON.stringify(newValues));
|
this.formValues = JSON.parse(JSON.stringify(newValues));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleImageLoaded() {
|
||||||
|
// Handle image loaded events from ChatMessage component
|
||||||
|
// This method can be used for layout adjustments if needed
|
||||||
|
console.log('Image loaded in active AI message');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -500,4 +531,28 @@ export default {
|
|||||||
0% { transform: rotate(0deg); }
|
0% { transform: rotate(0deg); }
|
||||||
100% { transform: rotate(360deg); }
|
100% { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Active AI Message Area - positioned at top of ChatInput */
|
||||||
|
.active-ai-message-area {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: var(--active-background-color);
|
||||||
|
color: var(--active-text-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure the active AI message integrates well with ChatInput styling */
|
||||||
|
.active-ai-message-area .message {
|
||||||
|
margin-bottom: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-ai-message-area .message-content {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
:task-id="message.taskId"
|
:task-id="message.taskId"
|
||||||
:api-prefix="apiPrefix"
|
:api-prefix="apiPrefix"
|
||||||
:is-latest-ai-message="isLatestAiMessage"
|
:is-latest-ai-message="isLatestAiMessage"
|
||||||
|
:is-in-input-area="isInInputArea"
|
||||||
class="message-progress"
|
class="message-progress"
|
||||||
@specialist-complete="handleSpecialistComplete"
|
@specialist-complete="handleSpecialistComplete"
|
||||||
@specialist-error="handleSpecialistError"
|
@specialist-error="handleSpecialistError"
|
||||||
@@ -169,11 +170,12 @@ export default {
|
|||||||
isLatestAiMessage: {
|
isLatestAiMessage: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
}
|
|
||||||
},
|
},
|
||||||
isInStickyArea: {
|
isInInputArea: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false },
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
emits: ['image-loaded', 'retry-message', 'specialist-complete', 'specialist-error'],
|
emits: ['image-loaded', 'retry-message', 'specialist-complete', 'specialist-error'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -310,6 +312,12 @@ export default {
|
|||||||
if (this.isInStickyArea) {
|
if (this.isInStickyArea) {
|
||||||
classes += " sticky-area";
|
classes += " sticky-area";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add class for messages in input area
|
||||||
|
if (this.isInInputArea) {
|
||||||
|
classes += " input-area";
|
||||||
|
}
|
||||||
|
|
||||||
return classes;
|
return classes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -338,12 +346,10 @@ export default {
|
|||||||
.message.ai.temporarily-at-bottom {
|
.message.ai.temporarily-at-bottom {
|
||||||
background-color: var(--active-background-color);
|
background-color: var(--active-background-color);
|
||||||
color: var(--active-text-color);
|
color: var(--active-text-color);
|
||||||
border-left: 3px solid var(--active-text-color);
|
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Styling for messages in sticky area - override history colors with active colors */
|
/* Styling for messages in sticky area - override history colors with active colors */
|
||||||
@@ -361,6 +367,14 @@ export default {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Active styling for messages in input area */
|
||||||
|
.message.input-area .message-content {
|
||||||
|
background-color: var(--active-background-color);
|
||||||
|
color: var(--active-text-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
.message-content {
|
.message-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
|
|||||||
@@ -297,6 +297,7 @@ export default {
|
|||||||
|
|
||||||
.dynamic-form {
|
.dynamic-form {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
box-shadow: 0 2px 15px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-header {
|
.form-header {
|
||||||
@@ -347,11 +348,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-field label {
|
.form-field label {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: #555;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-field input,
|
.form-field input,
|
||||||
@@ -396,7 +396,6 @@ export default {
|
|||||||
|
|
||||||
.checkbox-text {
|
.checkbox-text {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: #555;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-description {
|
.field-description {
|
||||||
|
|||||||
@@ -373,7 +373,6 @@ export default {
|
|||||||
|
|
||||||
.checkbox-text {
|
.checkbox-text {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: #555;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Context field styling */
|
/* Context field styling */
|
||||||
|
|||||||
@@ -174,10 +174,6 @@ export default {
|
|||||||
|
|
||||||
.language-selector {
|
.language-selector {
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: rgba(0, 0, 0, 0.2);
|
|
||||||
color: var(--sidebar-color);
|
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Empty state -->
|
<!-- Empty state -->
|
||||||
<div v-if="normalMessages.length === 0 && !stickyAiMessage" class="empty-state">
|
<div v-if="normalMessages.length === 0" class="empty-state">
|
||||||
<div class="empty-icon">💬</div>
|
<div class="empty-icon">💬</div>
|
||||||
<div class="empty-text">Nog geen berichten</div>
|
<div class="empty-text">Nog geen berichten</div>
|
||||||
<div class="empty-subtext">Start een gesprek door een bericht te typen!</div>
|
<div class="empty-subtext">Start een gesprek door een bericht te typen!</div>
|
||||||
@@ -35,18 +35,6 @@
|
|||||||
<typing-indicator v-if="isTyping"></typing-indicator>
|
<typing-indicator v-if="isTyping"></typing-indicator>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sticky bottom area for temporarily positioned AI messages -->
|
|
||||||
<div v-if="stickyAiMessage" class="sticky-ai-area">
|
|
||||||
<chat-message
|
|
||||||
:message="stickyAiMessage"
|
|
||||||
:is-submitting-form="isSubmittingForm"
|
|
||||||
:api-prefix="apiPrefix"
|
|
||||||
:is-latest-ai-message="isLatestAiMessage(stickyAiMessage)"
|
|
||||||
:is-in-sticky-area="true" @image-loaded="handleImageLoaded"
|
|
||||||
@specialist-complete="$emit('specialist-complete', $event)"
|
|
||||||
@specialist-error="$emit('specialist-error', $event)"
|
|
||||||
></chat-message>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -104,10 +92,6 @@ export default {
|
|||||||
normalMessages() {
|
normalMessages() {
|
||||||
return this.messages.filter(msg => !msg.isTemporarilyAtBottom);
|
return this.messages.filter(msg => !msg.isTemporarilyAtBottom);
|
||||||
},
|
},
|
||||||
// AI message that should be shown in sticky bottom area
|
|
||||||
stickyAiMessage() {
|
|
||||||
return this.messages.find(msg => msg.isTemporarilyAtBottom);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
messages: {
|
messages: {
|
||||||
@@ -291,17 +275,6 @@ export default {
|
|||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sticky bottom area for temporarily positioned AI messages */
|
|
||||||
.sticky-ai-area {
|
|
||||||
flex-shrink: 0;
|
|
||||||
max-height: 30%; /* Takes max 30% of available space */
|
|
||||||
border-top: 1px solid #e0e0e0;
|
|
||||||
background-color: var(--active-background-color);
|
|
||||||
color: var(--active-text-color);
|
|
||||||
padding: 10px;
|
|
||||||
overflow-y: auto;
|
|
||||||
box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-more-indicator {
|
.load-more-indicator {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
<div v-if="shouldShowFullTracker" class="progress-tracker" :class="{
|
<div v-if="shouldShowFullTracker" class="progress-tracker" :class="{
|
||||||
'expanded': isExpanded,
|
'expanded': isExpanded,
|
||||||
'completed': isCompleted && !hasError,
|
'completed': isCompleted && !hasError,
|
||||||
'error': error || hasError
|
'error': error || hasError,
|
||||||
|
'input-area': isInInputArea
|
||||||
}">
|
}">
|
||||||
<div
|
<div
|
||||||
class="progress-header"
|
class="progress-header"
|
||||||
@@ -106,6 +107,10 @@ export default {
|
|||||||
isLatestAiMessage: {
|
isLatestAiMessage: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
isInInputArea: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['specialist-complete', 'specialist-error'],
|
emits: ['specialist-complete', 'specialist-error'],
|
||||||
@@ -590,8 +595,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.working-animation-only {
|
.working-animation-only {
|
||||||
width: 24px;
|
width: 50px;
|
||||||
height: 24px;
|
height: 50px;
|
||||||
/* Evie working animatie styling */
|
/* Evie working animatie styling */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,4 +609,11 @@ export default {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
border: 1px solid #f44336;
|
border: 1px solid #f44336;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Input area specific styling */
|
||||||
|
|
||||||
|
|
||||||
|
.progress-tracker.input-area .progress-title {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -49,12 +49,11 @@ const handleImageError = () => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20px 15px;
|
padding: 20px 15px;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-image {
|
.logo-image {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 60px;
|
max-height: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ const props = defineProps({
|
|||||||
.sidebar-make-name {
|
.sidebar-make-name {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.make-name-text {
|
.make-name-text {
|
||||||
|
|||||||
231
test_sse_stream_fix.py
Normal file
231
test_sse_stream_fix.py
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test script to verify the SSE stream fix for ProgressTracker.
|
||||||
|
This script tests that:
|
||||||
|
1. apiPrefix prop flows correctly from ChatApp → ChatInput → ChatMessage → ProgressTracker
|
||||||
|
2. The SSE URL is constructed correctly with the apiPrefix
|
||||||
|
3. Both MessageHistory sticky area and ChatInput active area work properly
|
||||||
|
4. No 404 errors occur when accessing task progress endpoints
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def test_api_prefix_prop_flow():
|
||||||
|
"""Test that apiPrefix prop flows correctly through component hierarchy"""
|
||||||
|
|
||||||
|
print("🔍 Testing apiPrefix prop flow...")
|
||||||
|
|
||||||
|
# Test ChatApp.vue passes apiPrefix to ChatInput
|
||||||
|
chatapp_path = Path("/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/static/assets/vue-components/ChatApp.vue")
|
||||||
|
|
||||||
|
with open(chatapp_path, 'r', encoding='utf-8') as f:
|
||||||
|
chatapp_content = f.read()
|
||||||
|
|
||||||
|
if ':api-prefix="apiPrefix"' in chatapp_content:
|
||||||
|
print("✅ ChatApp passes apiPrefix to ChatInput")
|
||||||
|
else:
|
||||||
|
print("❌ ChatApp does not pass apiPrefix to ChatInput")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Test ChatInput.vue receives and passes apiPrefix to ChatMessage
|
||||||
|
chatinput_path = Path("/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/static/assets/vue-components/ChatInput.vue")
|
||||||
|
|
||||||
|
with open(chatinput_path, 'r', encoding='utf-8') as f:
|
||||||
|
chatinput_content = f.read()
|
||||||
|
|
||||||
|
# Check ChatInput has apiPrefix prop
|
||||||
|
if 'apiPrefix: {' in chatinput_content:
|
||||||
|
print("✅ ChatInput defines apiPrefix prop")
|
||||||
|
else:
|
||||||
|
print("❌ ChatInput missing apiPrefix prop")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check ChatInput passes apiPrefix to ChatMessage
|
||||||
|
if ':api-prefix="apiPrefix"' in chatinput_content:
|
||||||
|
print("✅ ChatInput passes apiPrefix to ChatMessage")
|
||||||
|
else:
|
||||||
|
print("❌ ChatInput does not pass apiPrefix to ChatMessage")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def test_progress_tracker_sse_url_construction():
|
||||||
|
"""Test that ProgressTracker constructs SSE URLs correctly"""
|
||||||
|
|
||||||
|
print("\n🔍 Testing ProgressTracker SSE URL construction...")
|
||||||
|
|
||||||
|
progresstracker_path = Path("/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/static/assets/vue-components/ProgressTracker.vue")
|
||||||
|
|
||||||
|
with open(progresstracker_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Check that ProgressTracker uses apiPrefix in URL construction
|
||||||
|
if 'const sseUrl = `${baseUrl}${this.apiPrefix}/api/task_progress/${this.taskId}`;' in content:
|
||||||
|
print("✅ ProgressTracker constructs SSE URL with apiPrefix")
|
||||||
|
else:
|
||||||
|
print("❌ ProgressTracker does not use apiPrefix in URL construction")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check that ProgressTracker has apiPrefix prop
|
||||||
|
if 'apiPrefix: {' in content:
|
||||||
|
print("✅ ProgressTracker defines apiPrefix prop")
|
||||||
|
else:
|
||||||
|
print("❌ ProgressTracker missing apiPrefix prop")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def test_chatmessage_prop_passing():
|
||||||
|
"""Test that ChatMessage passes apiPrefix to ProgressTracker"""
|
||||||
|
|
||||||
|
print("\n🔍 Testing ChatMessage prop passing...")
|
||||||
|
|
||||||
|
chatmessage_path = Path("/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/static/assets/vue-components/ChatMessage.vue")
|
||||||
|
|
||||||
|
with open(chatmessage_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Check that ChatMessage has apiPrefix prop
|
||||||
|
if 'apiPrefix: {' in content:
|
||||||
|
print("✅ ChatMessage defines apiPrefix prop")
|
||||||
|
else:
|
||||||
|
print("❌ ChatMessage missing apiPrefix prop")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check that ChatMessage passes apiPrefix to ProgressTracker
|
||||||
|
if ':api-prefix="apiPrefix"' in content:
|
||||||
|
print("✅ ChatMessage passes apiPrefix to ProgressTracker")
|
||||||
|
else:
|
||||||
|
print("❌ ChatMessage does not pass apiPrefix to ProgressTracker")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def test_backwards_compatibility():
|
||||||
|
"""Test that MessageHistory sticky area still works"""
|
||||||
|
|
||||||
|
print("\n🔍 Testing backwards compatibility...")
|
||||||
|
|
||||||
|
messagehistory_path = Path("/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/static/assets/vue-components/MessageHistory.vue")
|
||||||
|
|
||||||
|
with open(messagehistory_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Check that MessageHistory still has sticky area
|
||||||
|
if 'sticky-ai-area' in content:
|
||||||
|
print("✅ MessageHistory sticky area preserved")
|
||||||
|
else:
|
||||||
|
print("❌ MessageHistory sticky area removed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check that MessageHistory passes apiPrefix to ChatMessage
|
||||||
|
if ':api-prefix="apiPrefix"' in content:
|
||||||
|
print("✅ MessageHistory passes apiPrefix to ChatMessage")
|
||||||
|
else:
|
||||||
|
print("❌ MessageHistory does not pass apiPrefix to ChatMessage")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def test_expected_url_format():
|
||||||
|
"""Test that the expected URL format will be generated"""
|
||||||
|
|
||||||
|
print("\n🔍 Testing expected URL format...")
|
||||||
|
|
||||||
|
# Based on the error message, we expect URLs like:
|
||||||
|
# http://macstudio.ask-eve-ai-local.com/chat-client/chat/api/task_progress/616b3e7c-da47-4255-a900-68f9eb68e94f
|
||||||
|
|
||||||
|
# The apiPrefix should be '/chat-client/chat' based on chat.html template
|
||||||
|
chat_template_path = Path("/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/templates/chat.html")
|
||||||
|
|
||||||
|
with open(chat_template_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Check that apiPrefix is set correctly in chat.html
|
||||||
|
if 'apiPrefix: \'{{ request.headers.get("X-Forwarded-Prefix", "") }}/chat\'' in content:
|
||||||
|
print("✅ apiPrefix correctly configured in chat.html template")
|
||||||
|
print(" Expected format: /chat-client/chat/api/task_progress/{task_id}")
|
||||||
|
else:
|
||||||
|
print("❌ apiPrefix not correctly configured in chat.html template")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def test_component_integration():
|
||||||
|
"""Test that all components are properly integrated"""
|
||||||
|
|
||||||
|
print("\n🔍 Testing component integration...")
|
||||||
|
|
||||||
|
# Test that ChatInput imports ChatMessage
|
||||||
|
chatinput_path = Path("/Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/eveai_chat_client/static/assets/vue-components/ChatInput.vue")
|
||||||
|
|
||||||
|
with open(chatinput_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
if "import ChatMessage from './ChatMessage.vue';" in content:
|
||||||
|
print("✅ ChatInput imports ChatMessage")
|
||||||
|
else:
|
||||||
|
print("❌ ChatInput does not import ChatMessage")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if "'chat-message': ChatMessage" in content:
|
||||||
|
print("✅ ChatInput registers ChatMessage component")
|
||||||
|
else:
|
||||||
|
print("❌ ChatInput does not register ChatMessage component")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run all tests"""
|
||||||
|
|
||||||
|
print("🚀 Starting SSE Stream Fix Tests")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
tests = [
|
||||||
|
("API Prefix Prop Flow", test_api_prefix_prop_flow),
|
||||||
|
("ProgressTracker SSE URL Construction", test_progress_tracker_sse_url_construction),
|
||||||
|
("ChatMessage Prop Passing", test_chatmessage_prop_passing),
|
||||||
|
("Backwards Compatibility", test_backwards_compatibility),
|
||||||
|
("Expected URL Format", test_expected_url_format),
|
||||||
|
("Component Integration", test_component_integration)
|
||||||
|
]
|
||||||
|
|
||||||
|
passed = 0
|
||||||
|
total = len(tests)
|
||||||
|
|
||||||
|
for test_name, test_func in tests:
|
||||||
|
try:
|
||||||
|
if test_func():
|
||||||
|
passed += 1
|
||||||
|
print(f"✅ {test_name} - PASSED")
|
||||||
|
else:
|
||||||
|
print(f"❌ {test_name} - FAILED")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ {test_name} - ERROR: {e}")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print(f"📊 Test Results: {passed}/{total} tests passed")
|
||||||
|
|
||||||
|
if passed == total:
|
||||||
|
print("🎉 All tests passed! SSE stream fix is working correctly.")
|
||||||
|
print("\n📋 Fix Summary:")
|
||||||
|
print("• apiPrefix now flows correctly: ChatApp → ChatInput → ChatMessage → ProgressTracker")
|
||||||
|
print("• ProgressTracker will construct correct SSE URLs with /chat-client/chat prefix")
|
||||||
|
print("• Both MessageHistory sticky area and ChatInput active area work properly")
|
||||||
|
print("• No more 404 errors expected for task progress endpoints")
|
||||||
|
print("\n🔧 Changes Made:")
|
||||||
|
print("• Added :api-prefix=\"apiPrefix\" prop to ChatInput in ChatApp.vue")
|
||||||
|
print("• ChatInput already had correct apiPrefix prop definition and passing")
|
||||||
|
print("• All components in the chain now receive the apiPrefix correctly")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("⚠️ Some tests failed. Please review the implementation.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = main()
|
||||||
|
sys.exit(0 if success else 1)
|
||||||
Reference in New Issue
Block a user