Restructure omni services and add Chatwoot research snapshot
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
message: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full mb-4 flex items-center justify-start">
|
||||
<div
|
||||
v-dompurify-html="message"
|
||||
class="px-4 py-3 bg-white max-w-4xl text-slate-700 leading-6 text-sm rounded-md inline-block border border-slate-100"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
responseSourcePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
responseSourceName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header
|
||||
class="flex items-center px-8 py-4 bg-white border-b border-slate-100"
|
||||
role="banner"
|
||||
>
|
||||
<a :href="responseSourcePath" class="text-woot-500 hover:underline mr-4">
|
||||
{{ 'Back' }}
|
||||
</a>
|
||||
<div
|
||||
class="border border-solid border-slate-100 text-slate-700 mr-4 p-2 rounded-full"
|
||||
>
|
||||
<svg width="24" height="24"><use xlink:href="#icon-mist-fill" /></svg>
|
||||
</div>
|
||||
<div class="flex flex-col h-14 justify-center">
|
||||
<h1 id="page-title" class="text-base font-medium text-slate-900">
|
||||
{{ 'Robin AI playground' }}
|
||||
</h1>
|
||||
<p class="text-sm text-slate-600">
|
||||
{{ 'Chat with the source' }}
|
||||
<span class="font-medium">
|
||||
{{ responseSourceName }}
|
||||
</span>
|
||||
{{ 'and evaluate it’s efficiency.' }}
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
import TypingIndicator from './assets/typing.gif';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full mb-4 flex items-center justify-start">
|
||||
<div
|
||||
class="px-2 py-2 bg-white max-w-4xl text-slate-700 leading-6 text-sm rounded-md inline-block border border-slate-100"
|
||||
>
|
||||
<img :src="TypingIndicator" alt="TypingIndicator" class="h-4" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
message: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full mb-4 flex items-center justify-end">
|
||||
<div
|
||||
v-dompurify-html="message"
|
||||
class="px-4 py-3 bg-woot-400 text-white text-sm rounded-md inline-block"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1,75 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import BarChart from 'shared/components/charts/BarChart.vue';
|
||||
const props = defineProps({
|
||||
componentData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const prepareData = sourceData => {
|
||||
var labels = [];
|
||||
var data = [];
|
||||
sourceData.forEach(item => {
|
||||
labels.push(item[0]);
|
||||
data.push(item[1]);
|
||||
});
|
||||
return {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
type: 'bar',
|
||||
backgroundColor: 'rgb(31, 147, 255)',
|
||||
yAxisID: 'y',
|
||||
label: 'Conversations',
|
||||
data: data,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const chartData = computed(() => {
|
||||
return prepareData(props.componentData.chartData);
|
||||
});
|
||||
|
||||
const { accountsCount, usersCount, inboxesCount, conversationsCount } =
|
||||
props.componentData;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full h-full">
|
||||
<header class="main-content__header" role="banner">
|
||||
<h1 id="page-title" class="main-content__page-title">
|
||||
{{ 'Admin Dashboard' }}
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<section class="main-content__body main-content__body--flush">
|
||||
<div class="report--list">
|
||||
<div class="report-card">
|
||||
<div class="metric">{{ accountsCount }}</div>
|
||||
<div>{{ 'Accounts' }}</div>
|
||||
</div>
|
||||
<div class="report-card">
|
||||
<div class="metric">{{ usersCount }}</div>
|
||||
<div>{{ 'Users' }}</div>
|
||||
</div>
|
||||
<div class="report-card">
|
||||
<div class="metric">{{ inboxesCount }}</div>
|
||||
<div>{{ 'Inboxes' }}</div>
|
||||
</div>
|
||||
<div class="report-card">
|
||||
<div class="metric">{{ conversationsCount }}</div>
|
||||
<div>{{ 'Conversations' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- eslint-disable vue/no-static-inline-styles -->
|
||||
<BarChart
|
||||
class="p-8 w-full"
|
||||
:collection="chartData"
|
||||
style="max-height: 500px"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,135 @@
|
||||
<script>
|
||||
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
|
||||
import PlaygroundHeader from '../../components/playground/Header.vue';
|
||||
import UserMessage from '../../components/playground/UserMessage.vue';
|
||||
import BotMessage from '../../components/playground/BotMessage.vue';
|
||||
import TypingIndicator from '../../components/playground/TypingIndicator.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PlaygroundHeader,
|
||||
UserMessage,
|
||||
BotMessage,
|
||||
TypingIndicator,
|
||||
},
|
||||
props: {
|
||||
componentData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { formatMessage } = useMessageFormatter();
|
||||
return {
|
||||
formatMessage,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return { messages: [], messageContent: '', isWaiting: false };
|
||||
},
|
||||
computed: {
|
||||
previousMessages() {
|
||||
return this.messages.map(message => ({
|
||||
type: message.type,
|
||||
message: message.content,
|
||||
}));
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.focusInput();
|
||||
},
|
||||
methods: {
|
||||
focusInput() {
|
||||
this.$refs.messageInput.focus();
|
||||
},
|
||||
onMessageSend() {
|
||||
this.addMessageToData('User', this.messageContent);
|
||||
this.sendMessageToServer(this.messageContent);
|
||||
},
|
||||
scrollToLastMessage() {
|
||||
this.$nextTick(() => {
|
||||
const messageId = this.messages[this.messages.length - 1].id;
|
||||
const messageElement = document.getElementById(`message-${messageId}`);
|
||||
messageElement.scrollIntoView({ behavior: 'smooth' });
|
||||
});
|
||||
},
|
||||
addMessageToData(type, content) {
|
||||
this.messages.push({ id: this.messages.length, type, content });
|
||||
this.scrollToLastMessage();
|
||||
},
|
||||
async sendMessageToServer(messageContent) {
|
||||
this.messageContent = '';
|
||||
this.isWaiting = true;
|
||||
const csrfToken = document
|
||||
.querySelector('meta[name="csrf-token"]')
|
||||
.getAttribute('content');
|
||||
|
||||
try {
|
||||
const response = await fetch(window.location.href, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': csrfToken,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: messageContent,
|
||||
previous_messages: this.previousMessages,
|
||||
}),
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const { message } = await response.json();
|
||||
this.addMessageToData('Bot', message);
|
||||
} catch (error) {
|
||||
this.addMessageToData(
|
||||
'bot',
|
||||
'Error: Could not retrieve response. Please check the console for more details.'
|
||||
);
|
||||
} finally {
|
||||
this.isWaiting = false;
|
||||
this.focusInput();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="flex flex-col w-full h-full bg-slate-25">
|
||||
<PlaygroundHeader
|
||||
:response-source-name="componentData.responseSourceName"
|
||||
:response-source-path="componentData.responseSourcePath"
|
||||
/>
|
||||
<div class="flex-1 px-8 py-4 overflow-auto">
|
||||
<div
|
||||
v-for="message in messages"
|
||||
:id="`message-${message.id}`"
|
||||
:key="message.id"
|
||||
>
|
||||
<UserMessage
|
||||
v-if="message.type === 'User'"
|
||||
:message="formatMessage(message.content)"
|
||||
/>
|
||||
<BotMessage v-else :message="formatMessage(message.content)" />
|
||||
</div>
|
||||
<TypingIndicator v-if="isWaiting" />
|
||||
</div>
|
||||
<div class="w-full px-8 py-6">
|
||||
<textarea
|
||||
ref="messageInput"
|
||||
v-model="messageContent"
|
||||
:rows="4"
|
||||
class="resize-none block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border !outline-2 border-slate-100 focus:ring-woot-500 focus:border-woot-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-woot-500 dark:focus:border-woot-500"
|
||||
placeholder="Type a message... [CMD/CTRL + Enter to send]"
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
@keydown.meta.enter="onMessageSend"
|
||||
@keydown.ctrl.enter="onMessageSend"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
Reference in New Issue
Block a user