stock-management-demo/src/components/ChatInterface.vue

117 lines
No EOL
3.7 KiB
Vue

<template>
<div class="q-pa-md column full-height">
<q-scroll-area
ref="scrollAreaRef"
class="col"
style="flex-grow: 1; overflow-x: visible; overflow-y: auto;"
>
<div v-for="(message, index) in messages" :key="index" class="q-mb-sm q-mx-md">
<q-chat-message
:name="message.sender.toUpperCase()"
:sent="message.sender === 'user'"
:bg-color="message.sender === 'user' ? 'primary' : 'grey-4'"
:text-color="message.sender === 'user' ? 'white' : 'black'"
>
<!-- Use v-html to render parsed markdown -->
<div v-if="!message.loading" v-html="parseMarkdown(message.content)" class="message-content"></div>
<!-- Optional: Add a spinner for a better loading visual -->
<template v-if="message.loading" v-slot:default>
<q-spinner-dots size="2em" />
</template>
</q-chat-message>
</div>
</q-scroll-area>
<q-separator />
<div class="q-pa-sm row items-center">
<q-input
v-model="newMessage"
outlined
dense
placeholder="Type a message..."
class="col"
@keyup.enter="sendMessage"
autogrow
/>
<q-btn
round
dense
flat
icon="send"
color="primary"
class="q-ml-sm"
@click="sendMessage"
:disable="!newMessage.trim()"
/>
</div>
</div>
</template>
<script setup>
import { ref, watch, nextTick } from 'vue';
import { QScrollArea, QChatMessage, QSpinnerDots } from 'quasar'; // Import QSpinnerDots
import { marked } from 'marked'; // Import marked
const props = defineProps({
messages: {
type: Array,
required: true,
default: () => [],
// Example message structure:
// { sender: 'Bot', content: 'Hello!', loading: false }
// { sender: 'You', content: 'Thinking...', loading: true }
},
});
const emit = defineEmits(['send-message']);
const newMessage = ref('');
const scrollAreaRef = ref(null);
const scrollToBottom = () => {
if (scrollAreaRef.value) {
const scrollTarget = scrollAreaRef.value.getScrollTarget();
const duration = 300; // Optional: animation duration
// Use getScrollTarget().scrollHeight for accurate height
scrollAreaRef.value.setScrollPosition('vertical', scrollTarget.scrollHeight, duration);
}
};
const sendMessage = () => {
const trimmedMessage = newMessage.value.trim();
if (trimmedMessage) {
emit('send-message', trimmedMessage);
newMessage.value = '';
// Ensure the scroll happens after the message is potentially added to the list
nextTick(() => {
scrollToBottom();
});
}
};
const parseMarkdown = (content) => {
// Basic check to prevent errors if content is not a string
if (typeof content !== 'string') {
return '';
}
// Configure marked options if needed (e.g., sanitization)
// marked.setOptions({ sanitize: true }); // Example: Enable sanitization
return marked(content);
};
// Scroll to bottom when messages change or component mounts
watch(() => props.messages, () => {
nextTick(() => {
scrollToBottom();
});
}, { deep: true, immediate: true });
</script>
<style>
.message-content p {
margin: 0;
padding: 0;
}
</style>