enchance ai

This commit is contained in:
Emily
2024-09-16 15:37:18 +02:00
parent c3904ebd55
commit e6adbf9c7b
11 changed files with 276 additions and 75 deletions

View File

@@ -9,10 +9,10 @@ const props = defineProps<{ title: string, sub?: string }>();
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="flex items-center"> <div class="flex items-center">
<div class="flex flex-col grow"> <div class="flex flex-col grow">
<div class="poppins font-semibold text-[1.1rem] md:text-[1.4rem] text-text"> <div class="poppins font-semibold text-[1rem] md:text-[1.3rem] text-text">
{{ props.title }} {{ props.title }}
</div> </div>
<div v-if="props.sub" class="poppins text-[.8rem] md:text-[1.1rem] text-text-sub"> <div v-if="props.sub" class="poppins text-[.7rem] md:text-[1rem] text-text-sub">
{{ props.sub }} {{ props.sub }}
</div> </div>
</div> </div>

View File

@@ -16,10 +16,10 @@ const emits = defineEmits<{
<template> <template>
<div class="flex gap-2 border-[1px] border-gray-400 p-1 md:p-2 rounded-xl"> <div class="flex gap-2 border-[1px] border-lyx-widget-lighter p-1 md:p-2 rounded-xl bg-lyx-widget">
<div @click="$emit('changeIndex', index)" v-for="(opt, index) of options" <div @click="$emit('changeIndex', index)" v-for="(opt, index) of options"
class="hover:bg-white/10 select-btn-animated cursor-pointer rounded-lg poppins font-semibold px-2 md:px-3 py-1 text-[.8rem] md:text-[1rem]" class="hover:bg-lyx-widget-lighter/60 select-btn-animated cursor-pointer rounded-lg poppins font-regular px-2 md:px-3 py-1 text-[.8rem] md:text-[1rem]"
:class="{ 'bg-accent hover:!bg-accent': currentIndex == index }"> :class="{ 'bg-lyx-widget-lighter hover:!bg-lyx-widget-lighter': currentIndex == index }">
{{ opt.label }} {{ opt.label }}
</div> </div>
</div> </div>

View File

@@ -5,13 +5,15 @@ import * as datefns from 'date-fns';
registerChartComponents(); registerChartComponents();
const errored = ref<boolean>(false);
const props = defineProps<{ const props = defineProps<{
labels: string[], labels: string[],
title: string, title: string,
datasets: { datasets: {
points: number[], points: number[],
color: string, color: string,
type: string, chartType: string,
name: string name: string
}[] }[]
}>(); }>();
@@ -67,7 +69,11 @@ const chartOptions = ref<ChartOptions<'line'>>({
const chartData = ref<ChartData<'line'>>({ const chartData = ref<ChartData<'line'>>({
labels: props.labels.map(e => { labels: props.labels.map(e => {
try {
return datefns.format(new Date(e), 'dd/MM'); return datefns.format(new Date(e), 'dd/MM');
} catch (ex) {
return e;
}
}), }),
datasets: props.datasets.map(e => ({ datasets: props.datasets.map(e => ({
data: e.points, data: e.points,
@@ -82,7 +88,7 @@ const chartData = ref<ChartData<'line'>>({
hoverBackgroundColor: e.color, hoverBackgroundColor: e.color,
hoverBorderColor: 'white', hoverBorderColor: 'white',
hoverBorderWidth: 2, hoverBorderWidth: 2,
type: e.type type: e.chartType
} as any)) } as any))
}); });
@@ -106,10 +112,18 @@ function createGradient(startColor: string) {
} }
onMounted(async () => { onMounted(async () => {
try {
chartData.value.datasets.forEach(dataset => { chartData.value.datasets.forEach(dataset => {
if (dataset.borderColor && dataset.borderColor.toString().startsWith('#')) {
dataset.backgroundColor = [createGradient(dataset.borderColor as string)] dataset.backgroundColor = [createGradient(dataset.borderColor as string)]
} else {
dataset.backgroundColor = [createGradient('#3d59a4')]
}
}); });
} catch (ex) {
errored.value = true;
console.error(ex);
}
}); });
@@ -117,5 +131,8 @@ onMounted(async () => {
<template> <template>
<LineChart ref="lineChartRef" v-bind="lineChartProps"> </LineChart> <div>
<div v-if="errored"> ERROR CREATING CHART </div>
<LineChart v-if="!errored" ref="lineChartRef" v-bind="lineChartProps"> </LineChart>
</div>
</template> </template>

View File

@@ -262,12 +262,19 @@ onMounted(async () => {
<template> <template>
<CardTitled title="Trend chart" sub="Easily match Visits, Unique sessions and Events trends." class="w-full"> <CardTitled title="Trend chart" sub="Easily match Visits, Unique sessions and Events trends." class="w-full">
<template #header> <template #header>
<SelectButton class="w-fit" @changeIndex="selectedLabelIndex = $event" :currentIndex="selectedLabelIndex" <SelectButton class="w-fit" @changeIndex="selectedLabelIndex = $event"
:options="selectLabels"> :currentIndex="selectedLabelIndex" :options="selectLabels">
</SelectButton> </SelectButton>
</template> </template>
<div class="flex gap-6 w-full justify-end"> <div class="flex gap-6 w-full justify-between">
<LyxUiButton type="secondary" to="/analyst">
<div class="flex items-center gap-2 px-10">
<i class="far fa-sparkles text-yellow-400"></i>
<div class="poppins text-lyx-text"> Ask AI </div>
</div>
</LyxUiButton>
<div class="flex gap-6">
<div v-for="(dataset, index) of chartData.datasets" class="flex gap-2 items-center text-[.9rem]"> <div v-for="(dataset, index) of chartData.datasets" class="flex gap-2 items-center text-[.9rem]">
<UCheckbox :ui="{ <UCheckbox :ui="{
color: `text-[${legendColors[index]}]` color: `text-[${legendColors[index]}]`
@@ -275,6 +282,7 @@ onMounted(async () => {
<label class="mt-[2px]"> {{ dataset.label }} </label> <label class="mt-[2px]"> {{ dataset.label }} </label>
</div> </div>
</div> </div>
</div>

View File

@@ -17,7 +17,7 @@ const sections: Section[] = [
entries: [ entries: [
{ label: 'Dashboard', to: '/', icon: 'fal fa-table-layout' }, { label: 'Dashboard', to: '/', icon: 'fal fa-table-layout' },
{ label: 'Events', to: '/events', icon: 'fal fa-square-bolt' }, { label: 'Events', to: '/events', icon: 'fal fa-square-bolt' },
{ label: 'Analyst', to: '/analyst', icon: 'fal fa-microchip-ai' }, { label: 'AI Analyst', to: '/analyst', icon: 'fal fa-sparkles' },
{ label: 'Insights (soon)', to: '#', icon: 'fal fa-lightbulb', disabled: true }, { label: 'Insights (soon)', to: '#', icon: 'fal fa-lightbulb', disabled: true },
{ label: 'Links (soon)', to: '#', icon: 'fal fa-globe-pointer', disabled: true }, { label: 'Links (soon)', to: '#', icon: 'fal fa-globe-pointer', disabled: true },
{ label: 'Integrations (soon)', to: '#', icon: 'fal fa-cube', disabled: true }, { label: 'Integrations (soon)', to: '#', icon: 'fal fa-cube', disabled: true },
@@ -29,6 +29,7 @@ const sections: Section[] = [
}, },
{ {
label: 'Slack support', icon: 'fab fa-slack', label: 'Slack support', icon: 'fab fa-slack',
to:'#',
premiumOnly: true, premiumOnly: true,
action() { action() {
if (isGuest.value === true) return; if (isGuest.value === true) return;

View File

@@ -35,11 +35,13 @@
"v-calendar": "^3.1.2", "v-calendar": "^3.1.2",
"vue": "^3.4.21", "vue": "^3.4.21",
"vue-chart-3": "^3.1.8", "vue-chart-3": "^3.1.8",
"vue-markdown-render": "^2.2.1",
"vue-router": "^4.3.0" "vue-router": "^4.3.0"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/ui": "^2.15.2", "@nuxt/ui": "^2.15.2",
"@types/jsonwebtoken": "^9.0.6", "@types/jsonwebtoken": "^9.0.6",
"@types/markdown-it": "^14.1.2",
"@types/nodemailer": "^6.4.15", "@types/nodemailer": "^6.4.15",
"@types/pdfkit": "^0.13.4", "@types/pdfkit": "^0.13.4",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",

View File

@@ -1,16 +1,19 @@
<script lang="ts" setup> <script lang="ts" setup>
import VueMarkdown from 'vue-markdown-render';
definePageMeta({ layout: 'dashboard' }); definePageMeta({ layout: 'dashboard' });
const activeProject = useActiveProject(); const activeProject = useActiveProject();
const { data: chatsList, refresh: reloadChatsList } = useFetch(`/api/ai/${activeProject.value?._id}/chats_list`, { const { data: chatsList, refresh: reloadChatsList } = useFetch(`/api/ai/${activeProject.value?._id}/chats_list`, {
...signHeaders(), ...signHeaders()
transform: (data) => {
return data?.toReversed();
}
}); });
const viewChatsList = computed(() => (chatsList.value || []).toReversed());
const { data: chatsRemaining, refresh: reloadChatsRemaining } = useFetch(`/api/ai/${activeProject.value?._id}/chats_remaining`, signHeaders()); const { data: chatsRemaining, refresh: reloadChatsRemaining } = useFetch(`/api/ai/${activeProject.value?._id}/chats_remaining`, signHeaders());
const currentText = ref<string>(""); const currentText = ref<string>("");
@@ -72,8 +75,10 @@ async function sendMessage() {
async function openChat(chat_id?: string) { async function openChat(chat_id?: string) {
menuOpen.value = false; menuOpen.value = false;
if (!activeProject.value) return; if (!activeProject.value) return;
if (!chat_id) {
currentChatMessages.value = []; currentChatMessages.value = [];
if (!chat_id) {
currentChatId.value = ''; currentChatId.value = '';
return; return;
} }
@@ -105,10 +110,10 @@ function onKeyDown(e: KeyboardEvent) {
const menuOpen = ref<boolean>(false); const menuOpen = ref<boolean>(false);
const defaultPrompts = [ const defaultPrompts = [
'How many visits i got last week ?', "Create a line chart with this data: \n[100, 200, 30, 300, 500, 40]",
'How many visits i got last month ?', "Create a chart with Events (bar) and Visits (line) data from last week.",
'How many visits i got today ?', "How many visits did I get last week?",
'How many events i got last week ?', "Create a line chart of last week's visits."
] ]
async function deleteChat(chat_id: string) { async function deleteChat(chat_id: string) {
@@ -123,6 +128,8 @@ async function deleteChat(chat_id: string) {
await reloadChatsList(); await reloadChatsList();
} }
const { visible: pricingDrawerVisible } = usePricingDrawer()
</script> </script>
<template> <template>
@@ -144,7 +151,7 @@ async function deleteChat(chat_id: string) {
</div> </div>
<div class="grid grid-cols-2 gap-4 mt-6" v-if="!isGuest"> <div class="grid grid-cols-2 gap-4 mt-6" v-if="!isGuest">
<div v-for="prompt of defaultPrompts" @click="currentText = prompt" <div v-for="prompt of defaultPrompts" @click="currentText = prompt"
class="bg-lyx-widget-light hover:bg-lyx-widget-lighter cursor-pointer p-4 rounded-lg poppins text-center"> class="bg-lyx-widget-light hover:bg-lyx-widget-lighter cursor-pointer p-4 rounded-lg poppins text-center whitespace-pre-wrap flex items-center justify-center text-[.9rem]">
{{ prompt }} {{ prompt }}
</div> </div>
</div> </div>
@@ -164,15 +171,20 @@ async function deleteChat(chat_id: string) {
<div class="flex items-center justify-center shrink-0"> <div class="flex items-center justify-center shrink-0">
<img class="h-[3.5rem] w-auto" :src="'analyst.png'"> <img class="h-[3.5rem] w-auto" :src="'analyst.png'">
</div> </div>
<div v-html="parseMessageContent(message.content)" <div class="max-w-[70%] text-text/90 ai-message">
class="max-w-[70%] text-text/90 whitespace-pre-wrap"> <vue-markdown :source="message.content" :options="{
html: true,
breaks: true,
}" />
</div> </div>
</div> </div>
<div v-if="message.charts && message.charts.length > 0" <div v-if="message.charts && message.charts.length > 0"
class="flex items-center gap-3 justify-start w-full poppins text-[1.1rem] flex-col mt-4"> class="flex items-center gap-3 justify-start w-full poppins text-[1.1rem] flex-col mt-4">
<div v-for="chart of message.charts" class="w-full"> <div v-for="chart of message.charts" class="w-full">
<AnalystComposableChart :datasets="chart.datasets" :labels="chart.labels" :title="chart.title"> <AnalystComposableChart :datasets="chart.datasets" :labels="chart.labels"
:title="chart.title">
</AnalystComposableChart> </AnalystComposableChart>
</div> </div>
</div> </div>
@@ -217,21 +229,26 @@ async function deleteChat(chat_id: string) {
<i @click="menuOpen = false" class="fas fa-close cursor-pointer"></i> <i @click="menuOpen = false" class="fas fa-close cursor-pointer"></i>
</div> </div>
<div class="poppins font-semibold text-[1.5rem]"> <div class="poppins font-semibold text-[1.5rem]">
Lit, your AI Analyst is here! What Lit can do for you?
</div> </div>
<div class="poppins text-text/75"> <div class="poppins text-text/75">
Ask anything you want on your analytics, Ask anything from your data history, visualize and overlap charts, explore events or metadata,
and understand more Trends and Key Points to take Strategic moves! and enjoy a highly personalized data analysis experience.
</div> </div>
</div> </div>
<div class="flex gap-2 items-center py-3"> <div class="flex gap-2 items-center pt-3">
<div class="bg-accent w-5 h-5 rounded-full animate-pulse"> <div class="bg-accent w-5 h-5 rounded-full animate-pulse">
</div> </div>
<div class="manrope font-semibold"> {{ chatsRemaining }} remaining messages </div> <div class="manrope font-semibold"> {{ chatsRemaining }} remaining AI requests </div>
</div> </div>
<div class="poppins font-semibold text-[1.1rem]"> History: </div> <LyxUiButton type="primary" class="text-[.9rem] text-center w-full"
@click="pricingDrawerVisible = true">
Upgrade plan for more requests
</LyxUiButton>
<div class="poppins font-semibold text-[1.1rem]"> History </div>
<div class="px-2"> <div class="px-2">
<div @click="openChat()" <div @click="openChat()"
@@ -246,7 +263,7 @@ async function deleteChat(chat_id: string) {
<div class="flex flex-col gap-2 px-2"> <div class="flex flex-col gap-2 px-2">
<div :class="{ '!bg-accent/60': chat._id.toString() === currentChatId }" <div :class="{ '!bg-accent/60': chat._id.toString() === currentChatId }"
class="flex rounded-lg items-center gap-4 w-full px-4 bg-lyx-widget-lighter hover:bg-lyx-widget" class="flex rounded-lg items-center gap-4 w-full px-4 bg-lyx-widget-lighter hover:bg-lyx-widget"
v-for="chat of chatsList"> v-for="chat of viewChatsList">
<i @click="deleteChat(chat._id.toString())" <i @click="deleteChat(chat._id.toString())"
class="far fa-trash hover:text-gray-300 cursor-pointer"></i> class="far fa-trash hover:text-gray-300 cursor-pointer"></i>
<div @click="openChat(chat._id.toString())" <div @click="openChat(chat._id.toString())"
@@ -266,3 +283,82 @@ async function deleteChat(chat_id: string) {
</div> </div>
</template> </template>
<style lang="scss">
.ai-message {
/* Ensure headings stand out */
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: bold;
margin-top: 1.5em;
margin-bottom: 0.5em;
color: white;
}
/* Paragraphs for better line spacing */
p {
line-height: 1.8;
margin-bottom: 1em;
max-width: 750px;
/* Prevent very wide paragraphs for readability */
}
/* Blockquotes */
blockquote {
margin: 1.5em 10px;
padding: 10px 20px;
color: #555;
border-left: 5px solid #ccc;
background-color: #f5f5f5;
}
/* Code blocks */
pre {
background-color: #f4f4f4;
padding: 15px;
border-radius: 5px;
font-size: 14px;
overflow-x: auto;
}
code {
background-color: #f1f1f1;
padding: 2px 5px;
border-radius: 3px;
font-size: 90%;
}
/* Lists */
ul,
ol {
margin-left: 30px;
margin-bottom: 1.5em;
}
li {
margin-bottom: 0.5em;
}
/* Links */
a {
color: #007acc;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* Horizontal rules */
hr {
border: 1px solid #ddd;
margin: 2em 0;
}
}
</style>

111
dashboard/pnpm-lock.yaml generated
View File

@@ -74,6 +74,9 @@ importers:
vue-chart-3: vue-chart-3:
specifier: ^3.1.8 specifier: ^3.1.8
version: 3.1.8(chart.js@3.9.1)(vue@3.4.27(typescript@5.4.2)) version: 3.1.8(chart.js@3.9.1)(vue@3.4.27(typescript@5.4.2))
vue-markdown-render:
specifier: ^2.2.1
version: 2.2.1(vue@3.4.27(typescript@5.4.2))
vue-router: vue-router:
specifier: ^4.3.0 specifier: ^4.3.0
version: 4.3.2(vue@3.4.27(typescript@5.4.2)) version: 4.3.2(vue@3.4.27(typescript@5.4.2))
@@ -84,6 +87,9 @@ importers:
'@types/jsonwebtoken': '@types/jsonwebtoken':
specifier: ^9.0.6 specifier: ^9.0.6
version: 9.0.6 version: 9.0.6
'@types/markdown-it':
specifier: ^14.1.2
version: 14.1.2
'@types/nodemailer': '@types/nodemailer':
specifier: ^6.4.15 specifier: ^6.4.15
version: 6.4.15 version: 6.4.15
@@ -1171,9 +1177,18 @@ packages:
'@types/jsonwebtoken@9.0.6': '@types/jsonwebtoken@9.0.6':
resolution: {integrity: sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==} resolution: {integrity: sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==}
'@types/linkify-it@5.0.0':
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
'@types/lodash@4.17.7': '@types/lodash@4.17.7':
resolution: {integrity: sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==} resolution: {integrity: sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==}
'@types/markdown-it@14.1.2':
resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
'@types/mdurl@2.0.0':
resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
'@types/node-fetch@2.6.11': '@types/node-fetch@2.6.11':
resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==}
@@ -1431,20 +1446,20 @@ packages:
'@vue/reactivity@3.4.27': '@vue/reactivity@3.4.27':
resolution: {integrity: sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==} resolution: {integrity: sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==}
'@vue/reactivity@3.5.5': '@vue/reactivity@3.5.6':
resolution: {integrity: sha512-V4tTWElZQhT73PSK3Wnax9R9m4qvMX+LeKHnfylZc6SLh4Jc5/BPakp6e3zEhKWi5AN8TDzRkGnLkp8OqycYng==} resolution: {integrity: sha512-shZ+KtBoHna5GyUxWfoFVBCVd7k56m6lGhk5e+J9AKjheHF6yob5eukssHRI+rzvHBiU1sWs/1ZhNbLExc5oYQ==}
'@vue/runtime-core@3.4.27': '@vue/runtime-core@3.4.27':
resolution: {integrity: sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==} resolution: {integrity: sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==}
'@vue/runtime-core@3.5.5': '@vue/runtime-core@3.5.6':
resolution: {integrity: sha512-2/CFaRN17jgsXy4MpigWFBCAMmLkXPb4CjaHrndglwYSra7ajvkH2cat21dscuXaH91G8fXAeg5gCyxWJ+wCRA==} resolution: {integrity: sha512-FpFULR6+c2lI+m1fIGONLDqPQO34jxV8g6A4wBOgne8eSRHP6PQL27+kWFIx5wNhhjkO7B4rgtsHAmWv7qKvbg==}
'@vue/runtime-dom@3.4.27': '@vue/runtime-dom@3.4.27':
resolution: {integrity: sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==} resolution: {integrity: sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==}
'@vue/runtime-dom@3.5.5': '@vue/runtime-dom@3.5.6':
resolution: {integrity: sha512-0bQGgCuL+4Muz5PsCLgF4Ata9BTdhHi5VjsxtTDyI0Wy4MgoSvBGaA6bDc7W7CGgZOyirf9LNeetMYHQ05pgpw==} resolution: {integrity: sha512-SDPseWre45G38ENH2zXRAHL1dw/rr5qp91lS4lt/nHvMr0MhsbCbihGAWLXNB/6VfFOJe2O+RBRkXU+CJF7/sw==}
'@vue/server-renderer@3.4.27': '@vue/server-renderer@3.4.27':
resolution: {integrity: sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==} resolution: {integrity: sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==}
@@ -1454,8 +1469,8 @@ packages:
'@vue/shared@3.4.27': '@vue/shared@3.4.27':
resolution: {integrity: sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==} resolution: {integrity: sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==}
'@vue/shared@3.5.5': '@vue/shared@3.5.6':
resolution: {integrity: sha512-0KyMXyEgnmFAs6rNUL+6eUHtUCqCaNrVd+AW3MX3LyA0Yry5SA0Km03CDKiOua1x1WWnIr+W9+S0GMFoSDWERQ==} resolution: {integrity: sha512-eidH0HInnL39z6wAt6SFIwBrvGOpDWsDxlw3rCgo1B+CQ1781WzQUSU3YjxgdkcJo9Q8S6LmXTkvI+cLHGkQfA==}
'@vueuse/components@10.10.0': '@vueuse/components@10.10.0':
resolution: {integrity: sha512-HiA10NQ9HJAGnju+8ZK4TyA8LIc0a6BnJmVWDa/k+TRhaYCVacSDU04k0BQ2otV+gghUDdwu98upf6TDRXpoeg==} resolution: {integrity: sha512-HiA10NQ9HJAGnju+8ZK4TyA8LIc0a6BnJmVWDa/k+TRhaYCVacSDU04k0BQ2otV+gghUDdwu98upf6TDRXpoeg==}
@@ -2283,6 +2298,10 @@ packages:
resolution: {integrity: sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==} resolution: {integrity: sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
entities@3.0.1:
resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==}
engines: {node: '>=0.12'}
entities@4.5.0: entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
@@ -3179,6 +3198,9 @@ packages:
lines-and-columns@1.2.4: lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
linkify-it@4.0.1:
resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==}
listhen@1.7.2: listhen@1.7.2:
resolution: {integrity: sha512-7/HamOm5YD9Wb7CFgAZkKgVPA96WwhcTQoqtm2VTZGVbVVn3IWKRBTgrU7cchA3Q8k9iCsG8Osoi9GX4JsGM9g==} resolution: {integrity: sha512-7/HamOm5YD9Wb7CFgAZkKgVPA96WwhcTQoqtm2VTZGVbVVn3IWKRBTgrU7cchA3Q8k9iCsG8Osoi9GX4JsGM9g==}
hasBin: true hasBin: true
@@ -3281,12 +3303,19 @@ packages:
resolution: {integrity: sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==} resolution: {integrity: sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==}
engines: {node: ^16.14.0 || >=18.0.0} engines: {node: ^16.14.0 || >=18.0.0}
markdown-it@13.0.2:
resolution: {integrity: sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==}
hasBin: true
mdn-data@2.0.28: mdn-data@2.0.28:
resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
mdn-data@2.0.30: mdn-data@2.0.30:
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
mdurl@1.0.1:
resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
media-typer@0.3.0: media-typer@0.3.0:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@@ -4691,6 +4720,9 @@ packages:
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
uc.micro@1.0.6:
resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==}
ufo@1.5.3: ufo@1.5.3:
resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==}
@@ -5045,6 +5077,11 @@ packages:
vue-devtools-stub@0.1.0: vue-devtools-stub@0.1.0:
resolution: {integrity: sha512-RutnB7X8c5hjq39NceArgXg28WZtZpGc3+J16ljMiYnFhKvd8hITxSWQSQ5bvldxMDU6gG5mkxl1MTQLXckVSQ==} resolution: {integrity: sha512-RutnB7X8c5hjq39NceArgXg28WZtZpGc3+J16ljMiYnFhKvd8hITxSWQSQ5bvldxMDU6gG5mkxl1MTQLXckVSQ==}
vue-markdown-render@2.2.1:
resolution: {integrity: sha512-XkYnC0PMdbs6Vy6j/gZXSvCuOS0787Se5COwXlepRqiqPiunyCIeTPQAO2XnB4Yl04EOHXwLx5y6IuszMWSgyQ==}
peerDependencies:
vue: ^3.3.4
vue-observe-visibility@2.0.0-alpha.1: vue-observe-visibility@2.0.0-alpha.1:
resolution: {integrity: sha512-flFbp/gs9pZniXR6fans8smv1kDScJ8RS7rEpMjhVabiKeq7Qz3D9+eGsypncjfIyyU84saU88XZ0zjbD6Gq/g==} resolution: {integrity: sha512-flFbp/gs9pZniXR6fans8smv1kDScJ8RS7rEpMjhVabiKeq7Qz3D9+eGsypncjfIyyU84saU88XZ0zjbD6Gq/g==}
peerDependencies: peerDependencies:
@@ -6547,8 +6584,17 @@ snapshots:
dependencies: dependencies:
'@types/node': 20.12.12 '@types/node': 20.12.12
'@types/linkify-it@5.0.0': {}
'@types/lodash@4.17.7': {} '@types/lodash@4.17.7': {}
'@types/markdown-it@14.1.2':
dependencies:
'@types/linkify-it': 5.0.0
'@types/mdurl': 2.0.0
'@types/mdurl@2.0.0': {}
'@types/node-fetch@2.6.11': '@types/node-fetch@2.6.11':
dependencies: dependencies:
'@types/node': 20.12.12 '@types/node': 20.12.12
@@ -7001,7 +7047,7 @@ snapshots:
'@volar/language-core': 1.11.1 '@volar/language-core': 1.11.1
'@volar/source-map': 1.11.1 '@volar/source-map': 1.11.1
'@vue/compiler-dom': 3.4.27 '@vue/compiler-dom': 3.4.27
'@vue/shared': 3.5.5 '@vue/shared': 3.5.6
computeds: 0.0.1 computeds: 0.0.1
minimatch: 9.0.4 minimatch: 9.0.4
muggle-string: 0.3.1 muggle-string: 0.3.1
@@ -7014,19 +7060,19 @@ snapshots:
dependencies: dependencies:
'@vue/shared': 3.4.27 '@vue/shared': 3.4.27
'@vue/reactivity@3.5.5': '@vue/reactivity@3.5.6':
dependencies: dependencies:
'@vue/shared': 3.5.5 '@vue/shared': 3.5.6
'@vue/runtime-core@3.4.27': '@vue/runtime-core@3.4.27':
dependencies: dependencies:
'@vue/reactivity': 3.4.27 '@vue/reactivity': 3.4.27
'@vue/shared': 3.4.27 '@vue/shared': 3.4.27
'@vue/runtime-core@3.5.5': '@vue/runtime-core@3.5.6':
dependencies: dependencies:
'@vue/reactivity': 3.5.5 '@vue/reactivity': 3.5.6
'@vue/shared': 3.5.5 '@vue/shared': 3.5.6
'@vue/runtime-dom@3.4.27': '@vue/runtime-dom@3.4.27':
dependencies: dependencies:
@@ -7034,11 +7080,11 @@ snapshots:
'@vue/shared': 3.4.27 '@vue/shared': 3.4.27
csstype: 3.1.3 csstype: 3.1.3
'@vue/runtime-dom@3.5.5': '@vue/runtime-dom@3.5.6':
dependencies: dependencies:
'@vue/reactivity': 3.5.5 '@vue/reactivity': 3.5.6
'@vue/runtime-core': 3.5.5 '@vue/runtime-core': 3.5.6
'@vue/shared': 3.5.5 '@vue/shared': 3.5.6
csstype: 3.1.3 csstype: 3.1.3
'@vue/server-renderer@3.4.27(vue@3.4.27(typescript@5.4.2))': '@vue/server-renderer@3.4.27(vue@3.4.27(typescript@5.4.2))':
@@ -7049,7 +7095,7 @@ snapshots:
'@vue/shared@3.4.27': {} '@vue/shared@3.4.27': {}
'@vue/shared@3.5.5': {} '@vue/shared@3.5.6': {}
'@vueuse/components@10.10.0(vue@3.4.27(typescript@5.4.2))': '@vueuse/components@10.10.0(vue@3.4.27(typescript@5.4.2))':
dependencies: dependencies:
@@ -7840,6 +7886,8 @@ snapshots:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
tapable: 2.2.1 tapable: 2.2.1
entities@3.0.1: {}
entities@4.5.0: {} entities@4.5.0: {}
env-paths@2.2.1: {} env-paths@2.2.1: {}
@@ -8867,6 +8915,10 @@ snapshots:
lines-and-columns@1.2.4: {} lines-and-columns@1.2.4: {}
linkify-it@4.0.1:
dependencies:
uc.micro: 1.0.6
listhen@1.7.2: listhen@1.7.2:
dependencies: dependencies:
'@parcel/watcher': 2.4.1 '@parcel/watcher': 2.4.1
@@ -8986,10 +9038,20 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
markdown-it@13.0.2:
dependencies:
argparse: 2.0.1
entities: 3.0.1
linkify-it: 4.0.1
mdurl: 1.0.1
uc.micro: 1.0.6
mdn-data@2.0.28: {} mdn-data@2.0.28: {}
mdn-data@2.0.30: {} mdn-data@2.0.30: {}
mdurl@1.0.1: {}
media-typer@0.3.0: {} media-typer@0.3.0: {}
memory-pager@1.5.0: {} memory-pager@1.5.0: {}
@@ -10643,6 +10705,8 @@ snapshots:
typescript@5.4.2: {} typescript@5.4.2: {}
uc.micro@1.0.6: {}
ufo@1.5.3: {} ufo@1.5.3: {}
ultrahtml@1.5.3: {} ultrahtml@1.5.3: {}
@@ -11042,8 +11106,8 @@ snapshots:
vue-chart-3@3.1.8(chart.js@3.9.1)(vue@3.4.27(typescript@5.4.2)): vue-chart-3@3.1.8(chart.js@3.9.1)(vue@3.4.27(typescript@5.4.2)):
dependencies: dependencies:
'@vue/runtime-core': 3.5.5 '@vue/runtime-core': 3.5.6
'@vue/runtime-dom': 3.5.5 '@vue/runtime-dom': 3.5.6
chart.js: 3.9.1 chart.js: 3.9.1
csstype: 3.1.3 csstype: 3.1.3
lodash-es: 4.17.21 lodash-es: 4.17.21
@@ -11055,6 +11119,11 @@ snapshots:
vue-devtools-stub@0.1.0: {} vue-devtools-stub@0.1.0: {}
vue-markdown-render@2.2.1(vue@3.4.27(typescript@5.4.2)):
dependencies:
markdown-it: 13.0.2
vue: 3.4.27(typescript@5.4.2)
vue-observe-visibility@2.0.0-alpha.1(vue@3.4.27(typescript@5.4.2)): vue-observe-visibility@2.0.0-alpha.1(vue@3.4.27(typescript@5.4.2)):
dependencies: dependencies:
vue: 3.4.27(typescript@5.4.2) vue: 3.4.27(typescript@5.4.2)

View File

@@ -43,7 +43,7 @@ export class AiComposableChart extends AIPlugin<['createComposableChart']> {
}, },
color: { color: {
type: 'string', type: 'string',
description: 'Color used to represent the dataset' description: 'Color used to represent the dataset in format "#RRGGBB"'
}, },
name: { name: {
type: 'string', type: 'string',

View File

@@ -1,5 +1,5 @@
import { EventModel } from "@schema/metrics/EventSchema"; import { EventModel } from "@schema/metrics/EventSchema";
import { executeTimelineAggregation, fillAndMergeTimelineAggregationV2 } from "~/server/services/TimelineService"; import { AdvancedTimelineAggregationOptions, executeAdvancedTimelineAggregation, executeTimelineAggregation, fillAndMergeTimelineAggregationV2 } from "~/server/services/TimelineService";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { AIPlugin, AIPlugin_TTool } from "../Plugin"; import { AIPlugin, AIPlugin_TTool } from "../Plugin";
@@ -32,6 +32,8 @@ const getEventsTimelineTool: AIPlugin_TTool<'getEventsTimeline'> = {
properties: { properties: {
from: { type: 'string', description: 'ISO string of start date including hours' }, from: { type: 'string', description: 'ISO string of start date including hours' },
to: { type: 'string', description: 'ISO string of end date including hours' }, to: { type: 'string', description: 'ISO string of end date including hours' },
name: { type: 'string', description: 'Name of the events to get' },
metadata: { type: 'object', description: 'Metadata of events to get' },
}, },
required: ['from', 'to'] required: ['from', 'to']
} }
@@ -60,12 +62,17 @@ export class AiEvents extends AIPlugin<['getEventsCount', 'getEventsTimeline']>
tool: getEventsCountTool tool: getEventsCountTool
}, },
'getEventsTimeline': { 'getEventsTimeline': {
handler: async (data: { project_id: string, from: string, to: string }) => { handler: async (data: { project_id: string, from: string, to: string, name?: string, metadata?: string }) => {
const timelineData = await executeTimelineAggregation({ const query: AdvancedTimelineAggregationOptions & { customMatch: Record<string, any> } = {
projectId: new Types.ObjectId(data.project_id) as any, projectId: new Types.ObjectId(data.project_id) as any,
model: EventModel, model: EventModel,
from: data.from, to: data.to, slice: 'day' from: data.from, to: data.to, slice: 'day',
}); customMatch: {}
}
if (data.metadata) query.customMatch.metadata = data.metadata;
if (data.name) query.customMatch.name = data.name;
const timelineData = await executeAdvancedTimelineAggregation(query);
const timelineFilledMerged = fillAndMergeTimelineAggregationV2(timelineData, 'day', data.from, data.to); const timelineFilledMerged = fillAndMergeTimelineAggregationV2(timelineData, 'day', data.from, data.to);
return { data: timelineFilledMerged }; return { data: timelineFilledMerged };
}, },

View File

@@ -76,7 +76,8 @@ export async function sendMessageOnChat(text: string, pid: string, initial_chat_
messages.push(...chatMessages); messages.push(...chatMessages);
} else { } else {
const roleMessage: OpenAI.Chat.Completions.ChatCompletionMessageParam = { const roleMessage: OpenAI.Chat.Completions.ChatCompletionMessageParam = {
role: 'system', content: "Today is " + new Date().toISOString() role: 'system',
content: + "Today is " + new Date().toISOString()
} }
messages.push(roleMessage); messages.push(roleMessage);
await addMessageToChat(roleMessage, chat_id); await addMessageToChat(roleMessage, chat_id);