mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-09 23:48:36 +01:00
Add advanced ai
This commit is contained in:
121
dashboard/components/analyst/ComposableChart.vue
Normal file
121
dashboard/components/analyst/ComposableChart.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<script setup lang="ts">
|
||||
import type { ChartData, ChartOptions } from 'chart.js';
|
||||
import { useLineChart, LineChart } from 'vue-chart-3';
|
||||
import * as datefns from 'date-fns';
|
||||
|
||||
registerChartComponents();
|
||||
|
||||
const props = defineProps<{
|
||||
labels: string[],
|
||||
title:string,
|
||||
datasets: {
|
||||
points: number[],
|
||||
color: string,
|
||||
type: string,
|
||||
name:string
|
||||
}[]
|
||||
}>();
|
||||
|
||||
const chartOptions = ref<ChartOptions<'line'>>({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
axis: 'x',
|
||||
includeInvisible: true
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
ticks: { display: true },
|
||||
grid: {
|
||||
display: true,
|
||||
drawBorder: false,
|
||||
color: '#CCCCCC22',
|
||||
// borderDash: [5, 10]
|
||||
},
|
||||
},
|
||||
x: {
|
||||
ticks: { display: true },
|
||||
grid: {
|
||||
display: true,
|
||||
drawBorder: false,
|
||||
color: '#CCCCCC22',
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: true },
|
||||
title: {
|
||||
display: true,
|
||||
text: props.title
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
titleFont: { size: 16, weight: 'bold' },
|
||||
bodyFont: { size: 14 },
|
||||
padding: 10,
|
||||
cornerRadius: 4,
|
||||
boxPadding: 10,
|
||||
caretPadding: 20,
|
||||
yAlign: 'bottom',
|
||||
xAlign: 'center',
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const chartData = ref<ChartData<'line'>>({
|
||||
labels: props.labels.map(e => {
|
||||
return datefns.format(new Date(e), 'dd/MM');
|
||||
}),
|
||||
datasets: props.datasets.map(e => ({
|
||||
data: e.points,
|
||||
label: e.name,
|
||||
backgroundColor: [e.color + '77'],
|
||||
borderColor: e.color,
|
||||
borderWidth: 4,
|
||||
fill: true,
|
||||
tension: 0.45,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 10,
|
||||
hoverBackgroundColor: e.color,
|
||||
hoverBorderColor: 'white',
|
||||
hoverBorderWidth: 2,
|
||||
type: e.type
|
||||
} as any))
|
||||
});
|
||||
|
||||
|
||||
const { lineChartProps, lineChartRef } = useLineChart({ chartData: chartData, options: chartOptions });
|
||||
|
||||
function createGradient(startColor: string) {
|
||||
const c = document.createElement('canvas');
|
||||
const ctx = c.getContext("2d");
|
||||
let gradient: any = `${startColor}22`;
|
||||
if (ctx) {
|
||||
gradient = ctx.createLinearGradient(0, 25, 0, 300);
|
||||
gradient.addColorStop(0, `${startColor}99`);
|
||||
gradient.addColorStop(0.35, `${startColor}66`);
|
||||
gradient.addColorStop(1, `${startColor}22`);
|
||||
} else {
|
||||
console.warn('Cannot get context for gradient');
|
||||
}
|
||||
|
||||
return gradient;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
chartData.value.datasets.forEach(dataset => {
|
||||
dataset.backgroundColor = [createGradient(dataset.borderColor as string)]
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<LineChart ref="lineChartRef" v-bind="lineChartProps"> </LineChart>
|
||||
</template>
|
||||
110
dashboard/components/analyst/LineChart.vue
Normal file
110
dashboard/components/analyst/LineChart.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<script setup lang="ts">
|
||||
import type { ChartData, ChartOptions } from 'chart.js';
|
||||
import { useLineChart, LineChart } from 'vue-chart-3';
|
||||
registerChartComponents();
|
||||
|
||||
const props = defineProps<{
|
||||
data: any[],
|
||||
labels: string[]
|
||||
color: string,
|
||||
}>();
|
||||
|
||||
const chartOptions = ref<ChartOptions<'line'>>({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
axis: 'x',
|
||||
includeInvisible: true
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
ticks: { display: true },
|
||||
grid: {
|
||||
display: true,
|
||||
drawBorder: false,
|
||||
color: '#CCCCCC22',
|
||||
// borderDash: [5, 10]
|
||||
},
|
||||
},
|
||||
x: {
|
||||
ticks: { display: true },
|
||||
grid: {
|
||||
display: true,
|
||||
drawBorder: false,
|
||||
color: '#CCCCCC22',
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
title: { display: false },
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
titleFont: { size: 16, weight: 'bold' },
|
||||
bodyFont: { size: 14 },
|
||||
padding: 10,
|
||||
cornerRadius: 4,
|
||||
boxPadding: 10,
|
||||
caretPadding: 20,
|
||||
yAlign: 'bottom',
|
||||
xAlign: 'center',
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const chartData = ref<ChartData<'line'>>({
|
||||
labels: props.labels,
|
||||
datasets: [
|
||||
{
|
||||
data: props.data,
|
||||
backgroundColor: [props.color + '77'],
|
||||
borderColor: props.color,
|
||||
borderWidth: 4,
|
||||
fill: true,
|
||||
tension: 0.45,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 10,
|
||||
hoverBackgroundColor: props.color,
|
||||
hoverBorderColor: 'white',
|
||||
hoverBorderWidth: 2,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
const { lineChartProps, lineChartRef } = useLineChart({ chartData: chartData, options: chartOptions });
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
const c = document.createElement('canvas');
|
||||
const ctx = c.getContext("2d");
|
||||
let gradient: any = `${props.color}22`;
|
||||
if (ctx) {
|
||||
gradient = ctx.createLinearGradient(0, 25, 0, 300);
|
||||
gradient.addColorStop(0, `${props.color}99`);
|
||||
gradient.addColorStop(0.35, `${props.color}66`);
|
||||
gradient.addColorStop(1, `${props.color}22`);
|
||||
} else {
|
||||
console.warn('Cannot get context for gradient');
|
||||
}
|
||||
|
||||
chartData.value.datasets[0].backgroundColor = [gradient];
|
||||
|
||||
watch(props, () => {
|
||||
chartData.value.labels = props.labels;
|
||||
chartData.value.datasets[0].data = props.data;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<LineChart ref="lineChartRef" v-bind="lineChartProps"> </LineChart>
|
||||
</template>
|
||||
@@ -73,12 +73,17 @@ watch(activeProject, () => {
|
||||
|
||||
|
||||
const entries: SettingsTemplateEntry[] = [
|
||||
// { id: 'info', title: 'Billing informations', text: 'Manage billing informations for this project' },
|
||||
{ id: 'plan', title: 'Current plan', text: 'Manage current plat for this project' },
|
||||
{ id: 'usage', title: 'Usage', text: 'Show usage of current project' },
|
||||
{ id: 'invoices', title: 'Invoices', text: 'Manage invoices of current project' },
|
||||
]
|
||||
|
||||
|
||||
const currentBillingInfo = ref<any>({
|
||||
address: ''
|
||||
});
|
||||
|
||||
const { visible } = usePricingDrawer();
|
||||
|
||||
</script>
|
||||
@@ -114,6 +119,9 @@ const { visible } = usePricingDrawer();
|
||||
<div class="poppins font-semibold text-[2rem]"> €
|
||||
{{ getPremiumPrice(planData.premium_type) }} </div>
|
||||
<div class="poppins text-text-sub mt-2"> per month </div>
|
||||
<div class="flex items-center ml-2">
|
||||
<i class="far fa-info-circle text-[.8rem]"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
|
||||
@@ -31,7 +31,7 @@ const sections: Section[] = [
|
||||
label: 'Slack support', icon: 'fab fa-slack',
|
||||
premiumOnly: true,
|
||||
action() {
|
||||
if (isGuest) return;
|
||||
if (isGuest.value === true) return;
|
||||
if (isPremium.value === true) {
|
||||
window.open('https://join.slack.com/t/litlyx/shared_invite/zt-2q3oawn29-hZlu_fBUBlc4052Ooe3FZg', '_blank');
|
||||
} else {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"nodemailer": "^6.9.13",
|
||||
"nuxt": "^3.11.2",
|
||||
"nuxt-vue3-google-signin": "^0.0.11",
|
||||
"openai": "^4.47.1",
|
||||
"openai": "^4.61.0",
|
||||
"pdfkit": "^0.15.0",
|
||||
"primevue": "^3.52.0",
|
||||
"redis": "^4.6.13",
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({ layout: 'dashboard' });
|
||||
|
||||
definePageMeta({ layout: 'dashboard' });
|
||||
|
||||
const activeProject = useActiveProject();
|
||||
|
||||
const { data: chatsList, refresh: reloadChatsList } = useFetch(`/api/ai/${activeProject.value?._id}/chats_list`, signHeaders());
|
||||
const { data: chatsList, refresh: reloadChatsList } = useFetch(`/api/ai/${activeProject.value?._id}/chats_list`, {
|
||||
...signHeaders(),
|
||||
transform: (data) => {
|
||||
return data?.toReversed();
|
||||
}
|
||||
});
|
||||
|
||||
const { data: chatsRemaining, refresh: reloadChatsRemaining } = useFetch(`/api/ai/${activeProject.value?._id}/chats_remaining`, signHeaders());
|
||||
|
||||
@@ -12,7 +17,7 @@ const currentText = ref<string>("");
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
const currentChatId = ref<string>("");
|
||||
const currentChatMessages = ref<any[]>([]);
|
||||
const currentChatMessages = ref<{ role: string, content: string, charts?: any[] }[]>([]);
|
||||
|
||||
const scroller = ref<HTMLDivElement | null>(null);
|
||||
|
||||
@@ -39,7 +44,7 @@ async function sendMessage() {
|
||||
body: JSON.stringify(body),
|
||||
...signHeaders({ 'Content-Type': 'application/json' })
|
||||
});
|
||||
currentChatMessages.value.push({ role: 'assistant', content: res });
|
||||
currentChatMessages.value.push({ role: 'assistant', content: res.content || 'nocontent', charts: res.charts.map(e => JSON.parse(e)) });
|
||||
|
||||
await reloadChatsRemaining();
|
||||
await reloadChatsList();
|
||||
@@ -75,7 +80,8 @@ async function openChat(chat_id?: string) {
|
||||
currentChatId.value = chat_id;
|
||||
const messages = await $fetch(`/api/ai/${activeProject.value._id}/${chat_id}/get_messages`, signHeaders());
|
||||
if (!messages) return;
|
||||
currentChatMessages.value = messages;
|
||||
|
||||
currentChatMessages.value = messages.map(e => ({ ...e, charts: e.charts.map(k => JSON.parse(k)) })) as any;
|
||||
setTimeout(() => scrollToBottom(), 1);
|
||||
|
||||
}
|
||||
@@ -146,14 +152,15 @@ async function deleteChat(chat_id: string) {
|
||||
|
||||
<div ref="scroller" class="flex flex-col w-full gap-6 px-6 xl:px-28 overflow-y-auto pb-20">
|
||||
|
||||
<div class="flex w-full" v-for="message of currentChatMessages">
|
||||
<div class="flex w-full flex-col" v-for="message of currentChatMessages">
|
||||
|
||||
<div class="flex justify-end w-full poppins text-[1.1rem]" v-if="message.role === 'user'">
|
||||
<div class="bg-lyx-widget-light px-5 py-3 rounded-lg">
|
||||
{{ message.content }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 justify-start w-full poppins text-[1.1rem]"
|
||||
v-if="message.role === 'assistant'">
|
||||
v-if="message.role === 'assistant' && message.content">
|
||||
<div class="flex items-center justify-center shrink-0">
|
||||
<img class="h-[3.5rem] w-auto" :src="'analyst.png'">
|
||||
</div>
|
||||
@@ -161,6 +168,15 @@ async function deleteChat(chat_id: string) {
|
||||
class="max-w-[70%] text-text/90 whitespace-pre-wrap">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<div v-for="chart of message.charts" class="w-full">
|
||||
<AnalystComposableChart :datasets="chart.datasets" :labels="chart.labels" :title="chart.title">
|
||||
</AnalystComposableChart>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-if="loading"
|
||||
@@ -228,7 +244,9 @@ async function deleteChat(chat_id: string) {
|
||||
|
||||
<div class="overflow-y-auto">
|
||||
<div class="flex flex-col gap-2 px-2">
|
||||
<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" v-for="chat of chatsList?.toReversed()">
|
||||
<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"
|
||||
v-for="chat of chatsList">
|
||||
<i @click="deleteChat(chat._id.toString())"
|
||||
class="far fa-trash hover:text-gray-300 cursor-pointer"></i>
|
||||
<div @click="openChat(chat._id.toString())"
|
||||
|
||||
73
dashboard/pnpm-lock.yaml
generated
73
dashboard/pnpm-lock.yaml
generated
@@ -48,8 +48,8 @@ importers:
|
||||
specifier: ^0.0.11
|
||||
version: 0.0.11(@types/node@20.12.12)(rollup@4.18.0)(typescript@5.4.2)(vite@5.2.12(@types/node@20.12.12)(sass@1.77.2)(terser@5.31.0))(vue@3.4.27(typescript@5.4.2))
|
||||
openai:
|
||||
specifier: ^4.47.1
|
||||
version: 4.47.2(encoding@0.1.13)
|
||||
specifier: ^4.61.0
|
||||
version: 4.61.0(encoding@0.1.13)
|
||||
pdfkit:
|
||||
specifier: ^0.15.0
|
||||
version: 0.15.0
|
||||
@@ -1189,6 +1189,9 @@ packages:
|
||||
'@types/pdfkit@0.13.4':
|
||||
resolution: {integrity: sha512-ixGNDHYJCCKuamY305wbfYSphZ2WPe8FPkjn8oF4fHV+PgPV4V+hecPh2VOS2h4RNtpSB3zQcR4sCpNvvrEb1A==}
|
||||
|
||||
'@types/qs@6.9.16':
|
||||
resolution: {integrity: sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==}
|
||||
|
||||
'@types/resize-observer-browser@0.1.11':
|
||||
resolution: {integrity: sha512-cNw5iH8JkMkb3QkCoe7DaZiawbDQEUX8t7iuQaRTyLOyQCR2h+ibBD4GJt7p5yhUHrlOeL7ZtbxNHeipqNsBzQ==}
|
||||
|
||||
@@ -1428,20 +1431,20 @@ packages:
|
||||
'@vue/reactivity@3.4.27':
|
||||
resolution: {integrity: sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==}
|
||||
|
||||
'@vue/reactivity@3.5.4':
|
||||
resolution: {integrity: sha512-HKKbEuP7tYSGCq4e4nK6ZW6l5hyG66OUetefBp4budUyjvAYsnQDf+bgFzg2RAgnH0CInyqXwD9y47jwJEHrQw==}
|
||||
'@vue/reactivity@3.5.5':
|
||||
resolution: {integrity: sha512-V4tTWElZQhT73PSK3Wnax9R9m4qvMX+LeKHnfylZc6SLh4Jc5/BPakp6e3zEhKWi5AN8TDzRkGnLkp8OqycYng==}
|
||||
|
||||
'@vue/runtime-core@3.4.27':
|
||||
resolution: {integrity: sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==}
|
||||
|
||||
'@vue/runtime-core@3.5.4':
|
||||
resolution: {integrity: sha512-f3ek2sTA0AFu0n+w+kCtz567Euqqa3eHewvo4klwS7mWfSj/A+UmYTwsnUFo35KeyAFY60JgrCGvEBsu1n/3LA==}
|
||||
'@vue/runtime-core@3.5.5':
|
||||
resolution: {integrity: sha512-2/CFaRN17jgsXy4MpigWFBCAMmLkXPb4CjaHrndglwYSra7ajvkH2cat21dscuXaH91G8fXAeg5gCyxWJ+wCRA==}
|
||||
|
||||
'@vue/runtime-dom@3.4.27':
|
||||
resolution: {integrity: sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==}
|
||||
|
||||
'@vue/runtime-dom@3.5.4':
|
||||
resolution: {integrity: sha512-ofyc0w6rbD5KtjhP1i9hGOKdxGpvmuB1jprP7Djlj0X7R5J/oLwuNuE98GJ8WW31Hu2VxQHtk/LYTAlW8xrJdw==}
|
||||
'@vue/runtime-dom@3.5.5':
|
||||
resolution: {integrity: sha512-0bQGgCuL+4Muz5PsCLgF4Ata9BTdhHi5VjsxtTDyI0Wy4MgoSvBGaA6bDc7W7CGgZOyirf9LNeetMYHQ05pgpw==}
|
||||
|
||||
'@vue/server-renderer@3.4.27':
|
||||
resolution: {integrity: sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==}
|
||||
@@ -1451,8 +1454,8 @@ packages:
|
||||
'@vue/shared@3.4.27':
|
||||
resolution: {integrity: sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==}
|
||||
|
||||
'@vue/shared@3.5.4':
|
||||
resolution: {integrity: sha512-L2MCDD8l7yC62Te5UUyPVpmexhL9ipVnYRw9CsWfm/BGRL5FwDX4a25bcJ/OJSD3+Hx+k/a8LDKcG2AFdJV3BA==}
|
||||
'@vue/shared@3.5.5':
|
||||
resolution: {integrity: sha512-0KyMXyEgnmFAs6rNUL+6eUHtUCqCaNrVd+AW3MX3LyA0Yry5SA0Km03CDKiOua1x1WWnIr+W9+S0GMFoSDWERQ==}
|
||||
|
||||
'@vueuse/components@10.10.0':
|
||||
resolution: {integrity: sha512-HiA10NQ9HJAGnju+8ZK4TyA8LIc0a6BnJmVWDa/k+TRhaYCVacSDU04k0BQ2otV+gghUDdwu98upf6TDRXpoeg==}
|
||||
@@ -3704,9 +3707,14 @@ packages:
|
||||
resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
openai@4.47.2:
|
||||
resolution: {integrity: sha512-E3Wq9mYdDSLajmcJm9RO/lCegTKrQ7ilAkMbhob4UgGhTjHwIHI+mXNDNPl5+sGIUp2iVUkpoi772FjYa7JlqA==}
|
||||
openai@4.61.0:
|
||||
resolution: {integrity: sha512-xkygRBRLIUumxzKGb1ug05pWmJROQsHkGuj/N6Jiw2dj0dI19JvbFpErSZKmJ/DA+0IvpcugZqCAyk8iLpyM6Q==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
zod: ^3.23.8
|
||||
peerDependenciesMeta:
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
openapi-typescript@6.7.6:
|
||||
resolution: {integrity: sha512-c/hfooPx+RBIOPM09GSxABOZhYPblDoyaGhqBkD/59vtpN21jEuWKDlM0KYTvqJVlSYjKs0tBcIdeXKChlSPtw==}
|
||||
@@ -5084,10 +5092,6 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
web-streams-polyfill@3.3.3:
|
||||
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
web-streams-polyfill@4.0.0-beta.3:
|
||||
resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
|
||||
engines: {node: '>= 14'}
|
||||
@@ -6547,7 +6551,7 @@ snapshots:
|
||||
|
||||
'@types/node-fetch@2.6.11':
|
||||
dependencies:
|
||||
'@types/node': 18.19.33
|
||||
'@types/node': 20.12.12
|
||||
form-data: 4.0.0
|
||||
|
||||
'@types/node@18.19.33':
|
||||
@@ -6566,6 +6570,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/node': 20.12.12
|
||||
|
||||
'@types/qs@6.9.16': {}
|
||||
|
||||
'@types/resize-observer-browser@0.1.11': {}
|
||||
|
||||
'@types/resolve@1.20.2': {}
|
||||
@@ -6995,7 +7001,7 @@ snapshots:
|
||||
'@volar/language-core': 1.11.1
|
||||
'@volar/source-map': 1.11.1
|
||||
'@vue/compiler-dom': 3.4.27
|
||||
'@vue/shared': 3.5.4
|
||||
'@vue/shared': 3.5.5
|
||||
computeds: 0.0.1
|
||||
minimatch: 9.0.4
|
||||
muggle-string: 0.3.1
|
||||
@@ -7008,19 +7014,19 @@ snapshots:
|
||||
dependencies:
|
||||
'@vue/shared': 3.4.27
|
||||
|
||||
'@vue/reactivity@3.5.4':
|
||||
'@vue/reactivity@3.5.5':
|
||||
dependencies:
|
||||
'@vue/shared': 3.5.4
|
||||
'@vue/shared': 3.5.5
|
||||
|
||||
'@vue/runtime-core@3.4.27':
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.4.27
|
||||
'@vue/shared': 3.4.27
|
||||
|
||||
'@vue/runtime-core@3.5.4':
|
||||
'@vue/runtime-core@3.5.5':
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.5.4
|
||||
'@vue/shared': 3.5.4
|
||||
'@vue/reactivity': 3.5.5
|
||||
'@vue/shared': 3.5.5
|
||||
|
||||
'@vue/runtime-dom@3.4.27':
|
||||
dependencies:
|
||||
@@ -7028,11 +7034,11 @@ snapshots:
|
||||
'@vue/shared': 3.4.27
|
||||
csstype: 3.1.3
|
||||
|
||||
'@vue/runtime-dom@3.5.4':
|
||||
'@vue/runtime-dom@3.5.5':
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.5.4
|
||||
'@vue/runtime-core': 3.5.4
|
||||
'@vue/shared': 3.5.4
|
||||
'@vue/reactivity': 3.5.5
|
||||
'@vue/runtime-core': 3.5.5
|
||||
'@vue/shared': 3.5.5
|
||||
csstype: 3.1.3
|
||||
|
||||
'@vue/server-renderer@3.4.27(vue@3.4.27(typescript@5.4.2))':
|
||||
@@ -7043,7 +7049,7 @@ snapshots:
|
||||
|
||||
'@vue/shared@3.4.27': {}
|
||||
|
||||
'@vue/shared@3.5.4': {}
|
||||
'@vue/shared@3.5.5': {}
|
||||
|
||||
'@vueuse/components@10.10.0(vue@3.4.27(typescript@5.4.2))':
|
||||
dependencies:
|
||||
@@ -9585,16 +9591,17 @@ snapshots:
|
||||
is-docker: 2.2.1
|
||||
is-wsl: 2.2.0
|
||||
|
||||
openai@4.47.2(encoding@0.1.13):
|
||||
openai@4.61.0(encoding@0.1.13):
|
||||
dependencies:
|
||||
'@types/node': 18.19.33
|
||||
'@types/node-fetch': 2.6.11
|
||||
'@types/qs': 6.9.16
|
||||
abort-controller: 3.0.0
|
||||
agentkeepalive: 4.5.0
|
||||
form-data-encoder: 1.7.2
|
||||
formdata-node: 4.4.1
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
web-streams-polyfill: 3.3.3
|
||||
qs: 6.12.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
@@ -11035,8 +11042,8 @@ snapshots:
|
||||
|
||||
vue-chart-3@3.1.8(chart.js@3.9.1)(vue@3.4.27(typescript@5.4.2)):
|
||||
dependencies:
|
||||
'@vue/runtime-core': 3.5.4
|
||||
'@vue/runtime-dom': 3.5.4
|
||||
'@vue/runtime-core': 3.5.5
|
||||
'@vue/runtime-dom': 3.5.5
|
||||
chart.js: 3.9.1
|
||||
csstype: 3.1.3
|
||||
lodash-es: 4.17.21
|
||||
@@ -11105,8 +11112,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.4.2
|
||||
|
||||
web-streams-polyfill@3.3.3: {}
|
||||
|
||||
web-streams-polyfill@4.0.0-beta.3: {}
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
30
dashboard/server/ai/Plugin.ts
Normal file
30
dashboard/server/ai/Plugin.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
import type OpenAI from 'openai'
|
||||
|
||||
|
||||
export type AIPlugin_TTool<T extends string> = (OpenAI.Chat.Completions.ChatCompletionTool & { function: { name: T } });
|
||||
export type AIPlugin_TFunction<T extends string> = (...args: any[]) => any;
|
||||
|
||||
type AIPlugin_Constructor<Items extends string[]> = {
|
||||
[Key in Items[number]]: {
|
||||
tool: AIPlugin_TTool<Key>,
|
||||
handler: AIPlugin_TFunction<Key>
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class AIPlugin<Items extends string[] = []> {
|
||||
constructor(public functions: AIPlugin_Constructor<Items>) { }
|
||||
|
||||
getTools() {
|
||||
const keys = Object.keys(this.functions) as Items;
|
||||
return keys.map((key: Items[number]) => { return this.functions[key].tool });
|
||||
}
|
||||
getHandlers() {
|
||||
const keys = Object.keys(this.functions) as Items;
|
||||
const result: Record<string, any> = {};
|
||||
keys.forEach((key: Items[number]) => {
|
||||
result[key] = this.functions[key].handler;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
67
dashboard/server/ai/functions/AI_ComposableChart.ts
Normal file
67
dashboard/server/ai/functions/AI_ComposableChart.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
|
||||
import { AIPlugin } from "../Plugin";
|
||||
|
||||
export class AiComposableChart extends AIPlugin<['createComposableChart']> {
|
||||
constructor() {
|
||||
super({
|
||||
'createComposableChart': {
|
||||
handler: (data: { labels: string, points: number[] }) => {
|
||||
return { ok: true };
|
||||
},
|
||||
tool: {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'createComposableChart',
|
||||
description: 'Creates a chart based on the provided datasets',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
labels: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Labels for each data point in the chart'
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: 'Title of the chart to let user understand what is displaying, not include dates'
|
||||
},
|
||||
datasets: {
|
||||
type: 'array',
|
||||
description: 'List of datasets',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
chartType: {
|
||||
type: 'string',
|
||||
enum: ['line', 'bar'],
|
||||
description: 'The type of chart to display the dataset, either "line" or "bar"'
|
||||
},
|
||||
points: {
|
||||
type: 'array',
|
||||
items: { type: 'number' },
|
||||
description: 'Numerical values for each data point in the chart'
|
||||
},
|
||||
color: {
|
||||
type: 'string',
|
||||
description: 'Color used to represent the dataset'
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Name of the dataset'
|
||||
}
|
||||
},
|
||||
required: ['points', 'color', 'chartType', 'name'],
|
||||
description: 'Data points and style information for the dataset'
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['labels', 'datasets', 'title']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const AiComposableChartInstance = new AiComposableChart();
|
||||
80
dashboard/server/ai/functions/AI_Events.ts
Normal file
80
dashboard/server/ai/functions/AI_Events.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { EventModel } from "@schema/metrics/EventSchema";
|
||||
import { executeTimelineAggregation, fillAndMergeTimelineAggregationV2 } from "~/server/services/TimelineService";
|
||||
import { Types } from "mongoose";
|
||||
import { AIPlugin, AIPlugin_TTool } from "../Plugin";
|
||||
|
||||
|
||||
const getEventsCountTool: AIPlugin_TTool<'getEventsCount'> = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'getEventsCount',
|
||||
description: 'Gets the number of events received on a date range, can also specify the event name and the metadata associated',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
from: { type: 'string', description: 'ISO string of start 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']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getEventsTimelineTool: AIPlugin_TTool<'getEventsTimeline'> = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'getEventsTimeline',
|
||||
description: 'Gets an array of date and count for events received on a date range. Should be used to create charts.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
from: { type: 'string', description: 'ISO string of start date including hours' },
|
||||
to: { type: 'string', description: 'ISO string of end date including hours' },
|
||||
},
|
||||
required: ['from', 'to']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class AiEvents extends AIPlugin<['getEventsCount', 'getEventsTimeline']> {
|
||||
|
||||
constructor() {
|
||||
|
||||
super({
|
||||
'getEventsCount': {
|
||||
handler: async (data: { project_id: string, from?: string, to?: string, name?: string, metadata?: string }) => {
|
||||
const query: any = {
|
||||
project_id: data.project_id,
|
||||
created_at: {
|
||||
$gt: data.from ? new Date(data.from).getTime() : new Date(2023).getTime(),
|
||||
$lt: data.to ? new Date(data.to).getTime() : new Date().getTime(),
|
||||
}
|
||||
}
|
||||
if (data.metadata) query.metadata = data.metadata;
|
||||
if (data.name) query.name = data.name;
|
||||
const result = await EventModel.countDocuments(query);
|
||||
return { count: result };
|
||||
},
|
||||
tool: getEventsCountTool
|
||||
},
|
||||
'getEventsTimeline': {
|
||||
handler: async (data: { project_id: string, from: string, to: string }) => {
|
||||
const timelineData = await executeTimelineAggregation({
|
||||
projectId: new Types.ObjectId(data.project_id) as any,
|
||||
model: EventModel,
|
||||
from: data.from, to: data.to, slice: 'day'
|
||||
});
|
||||
const timelineFilledMerged = fillAndMergeTimelineAggregationV2(timelineData, 'day', data.from, data.to);
|
||||
return { data: timelineFilledMerged };
|
||||
},
|
||||
tool: getEventsTimelineTool
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export const AiEventsInstance = new AiEvents();
|
||||
|
||||
87
dashboard/server/ai/functions/AI_Visits.ts
Normal file
87
dashboard/server/ai/functions/AI_Visits.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { VisitModel } from "@schema/metrics/VisitSchema";
|
||||
import { AdvancedTimelineAggregationOptions, executeAdvancedTimelineAggregation, executeTimelineAggregation, fillAndMergeTimelineAggregationV2 } from "~/server/services/TimelineService";
|
||||
import { Types } from "mongoose";
|
||||
import { AIPlugin, AIPlugin_TTool } from "../Plugin";
|
||||
|
||||
|
||||
const getVisitsCountsTool: AIPlugin_TTool<'getVisitsCount'> = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'getVisitsCount',
|
||||
description: 'Gets the number of visits received on a date range',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
from: { type: 'string', description: 'ISO string of start date including hours' },
|
||||
to: { type: 'string', description: 'ISO string of end date including hours' },
|
||||
website: { type: 'string', description: 'The website of the visits' },
|
||||
page: { type: 'string', description: 'The page of the visit' }
|
||||
},
|
||||
required: ['from', 'to']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getVisitsTimelineTool: AIPlugin_TTool<'getVisitsTimeline'> = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'getVisitsTimeline',
|
||||
description: 'Gets an array of date and count for events received on a date range. Should be used to create charts.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
from: { type: 'string', description: 'ISO string of start date including hours' },
|
||||
to: { type: 'string', description: 'ISO string of end date including hours' },
|
||||
website: { type: 'string', description: 'The website of the visits' },
|
||||
page: { type: 'string', description: 'The page of the visit' }
|
||||
},
|
||||
required: ['from', 'to']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class AiVisits extends AIPlugin<['getVisitsCount', 'getVisitsTimeline']> {
|
||||
|
||||
constructor() {
|
||||
|
||||
super({
|
||||
'getVisitsCount': {
|
||||
handler: async (data: { project_id: string, from?: string, to?: string, website?: string, page?: string }) => {
|
||||
const query: any = {
|
||||
project_id: data.project_id,
|
||||
created_at: {
|
||||
$gt: data.from ? new Date(data.from).getTime() : new Date(2023).getTime(),
|
||||
$lt: data.to ? new Date(data.to).getTime() : new Date().getTime(),
|
||||
}
|
||||
}
|
||||
if (data.website) query.website = data.website;
|
||||
if (data.page) query.page = data.page;
|
||||
const result = await VisitModel.countDocuments(query);
|
||||
return { count: result };
|
||||
},
|
||||
tool: getVisitsCountsTool
|
||||
},
|
||||
'getVisitsTimeline': {
|
||||
handler: async (data: { project_id: string, from: string, to: string, website?: string, page?: string }) => {
|
||||
const query: AdvancedTimelineAggregationOptions & { customMatch: Record<string, any> } = {
|
||||
projectId: new Types.ObjectId(data.project_id) as any,
|
||||
model: VisitModel,
|
||||
from: data.from, to: data.to, slice: 'day',
|
||||
customMatch: {}
|
||||
}
|
||||
|
||||
if (data.website) query.customMatch.website = data.website;
|
||||
if (data.page) query.customMatch.page = data.page;
|
||||
|
||||
const timelineData = await executeAdvancedTimelineAggregation(query);
|
||||
const timelineFilledMerged = fillAndMergeTimelineAggregationV2(timelineData, 'day', data.from, data.to);
|
||||
return { data: timelineFilledMerged };
|
||||
},
|
||||
tool: getVisitsTimelineTool
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export const AiVisitsInstance = new AiVisits();
|
||||
@@ -1,8 +1,5 @@
|
||||
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
||||
import { AiChatModel } from "@schema/ai/AiChatSchema";
|
||||
import { sendMessageOnChat } from "~/server/services/AiService";
|
||||
|
||||
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
||||
import { AiChatModel } from "@schema/ai/AiChatSchema";
|
||||
import { sendMessageOnChat } from "~/server/services/AiService";
|
||||
|
||||
|
||||
import type OpenAI from "openai";
|
||||
import { getChartsInMessage } from "~/server/services/AiService";
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
@@ -19,11 +18,14 @@ export default defineEventHandler(async event => {
|
||||
const chat = await AiChatModel.findOne({ _id: chat_id, project_id });
|
||||
if (!chat) return;
|
||||
|
||||
const messages = chat.messages.filter(e => {
|
||||
return (e.role == 'user' || (e.role == 'assistant' && e.content != undefined))
|
||||
}).map(e => {
|
||||
return { role: e.role, content: e.content }
|
||||
});
|
||||
|
||||
return messages;
|
||||
return (chat.messages as OpenAI.Chat.Completions.ChatCompletionMessageParam[])
|
||||
.filter(e => e.role === 'assistant' || e.role === 'user')
|
||||
.map(e => {
|
||||
const charts = getChartsInMessage(e);
|
||||
const content = e.content;
|
||||
return { role: e.role, content, charts }
|
||||
})
|
||||
.filter(e=>{
|
||||
return e.charts.length > 0 || e.content
|
||||
})
|
||||
});
|
||||
@@ -23,5 +23,6 @@ export default defineEventHandler(async event => {
|
||||
if (chatsRemaining <= 0) return setResponseStatus(event, 400, 'CHAT_LIMIT_REACHED');
|
||||
|
||||
const response = await sendMessageOnChat(text, project._id.toString(), chat_id);
|
||||
return response || 'Error getting response';
|
||||
|
||||
return response;
|
||||
});
|
||||
@@ -1,51 +0,0 @@
|
||||
|
||||
import OpenAI from "openai";
|
||||
import { EventModel } from "@schema/metrics/EventSchema";
|
||||
|
||||
|
||||
export const AI_EventsFunctions = {
|
||||
getEventsCount: ({ pid, from, to, name, metadata }: any) => {
|
||||
return getEventsCountForAI(pid, from, to, name, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const getEventsCountForAIDeclaration: OpenAI.Chat.Completions.ChatCompletionTool = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'getEventsCount',
|
||||
description: 'Gets the number of events received on a date range, can also specify the event name and the metadata associated',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
from: { type: 'string', description: 'ISO string of start 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']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const AI_EventsTools: OpenAI.Chat.Completions.ChatCompletionTool[] = [
|
||||
getEventsCountForAIDeclaration
|
||||
]
|
||||
|
||||
export async function getEventsCountForAI(project_id: string, from?: string, to?: string, name?: string, metadata?: string) {
|
||||
|
||||
const query: any = {
|
||||
project_id,
|
||||
created_at: {
|
||||
$gt: from ? new Date(from).getTime() : new Date(2023).getTime(),
|
||||
$lt: to ? new Date(to).getTime() : new Date().getTime(),
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata) query.metadata = metadata;
|
||||
if (name) query.name = name;
|
||||
|
||||
const result = await EventModel.countDocuments(query);
|
||||
|
||||
return { count: result };
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { VisitModel } from "@schema/metrics/VisitSchema";
|
||||
|
||||
|
||||
|
||||
export async function getVisitsCountFromDateRange(project_id: string, from?: string, to?: string) {
|
||||
const result = await VisitModel.countDocuments({
|
||||
project_id,
|
||||
created_at: {
|
||||
$gt: from ? new Date(from).getTime() : new Date(2023).getTime(),
|
||||
$lt: to ? new Date(to).getTime() : new Date().getTime(),
|
||||
}
|
||||
});
|
||||
return { count: result };
|
||||
}
|
||||
@@ -8,11 +8,6 @@ const config = useRuntimeConfig();
|
||||
let connection: mongoose.Mongoose;
|
||||
|
||||
|
||||
let anomalyMinutesCount = 0;
|
||||
function anomalyCheck() {
|
||||
|
||||
}
|
||||
|
||||
export default async () => {
|
||||
|
||||
console.log('[SERVER] Initializing');
|
||||
|
||||
@@ -1,59 +1,35 @@
|
||||
|
||||
import { getVisitsCountFromDateRange } from '~/server/api/ai/functions/AI_Visits';
|
||||
|
||||
import OpenAI from "openai";
|
||||
import { AiChatModel } from '@schema/ai/AiChatSchema';
|
||||
import { AI_EventsFunctions, AI_EventsTools } from '../api/ai/functions/AI_Events';
|
||||
import { ProjectCountModel } from '@schema/ProjectsCounts';
|
||||
import { ProjectLimitModel } from '@schema/ProjectsLimits';
|
||||
|
||||
import { AiEventsInstance } from '../ai/functions/AI_Events';
|
||||
import { AiVisitsInstance } from '../ai/functions/AI_Visits';
|
||||
import { AiComposableChartInstance } from '../ai/functions/AI_ComposableChart';
|
||||
|
||||
const { AI_ORG, AI_PROJECT, AI_KEY } = useRuntimeConfig();
|
||||
|
||||
const OPENAI_MODEL: OpenAI.Chat.ChatModel = 'gpt-4o-mini';
|
||||
|
||||
const openai = new OpenAI({
|
||||
organization: AI_ORG,
|
||||
project: AI_PROJECT,
|
||||
apiKey: AI_KEY
|
||||
});
|
||||
|
||||
|
||||
// const get_current_date: OpenAI.Chat.Completions.ChatCompletionTool = {
|
||||
// type: 'function',
|
||||
// function: {
|
||||
// name: 'get_current_date',
|
||||
// description: 'Gets the current date as ISO string',
|
||||
// }
|
||||
// }
|
||||
|
||||
const get_visits_count_Schema: OpenAI.Chat.Completions.ChatCompletionTool = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'get_visits_count',
|
||||
description: 'Gets the number of visits received on a date range',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
from: { type: 'string', description: 'ISO string of start date including hours' },
|
||||
to: { type: 'string', description: 'ISO string of end date including hours' }
|
||||
},
|
||||
required: ['from', 'to']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const tools: OpenAI.Chat.Completions.ChatCompletionTool[] = [
|
||||
get_visits_count_Schema,
|
||||
...AI_EventsTools
|
||||
...AiVisitsInstance.getTools(),
|
||||
...AiEventsInstance.getTools(),
|
||||
...AiComposableChartInstance.getTools()
|
||||
]
|
||||
|
||||
|
||||
const functions: any = {
|
||||
get_current_date: async ({ }) => {
|
||||
return new Date().toISOString();
|
||||
},
|
||||
get_visits_count: async ({ pid, from, to }: any) => {
|
||||
return await getVisitsCountFromDateRange(pid, from, to);
|
||||
},
|
||||
...AI_EventsFunctions
|
||||
...AiVisitsInstance.getHandlers(),
|
||||
...AiEventsInstance.getHandlers(),
|
||||
...AiComposableChartInstance.getHandlers()
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +57,14 @@ async function setChatTitle(title: string, chat_id?: string) {
|
||||
await AiChatModel.updateOne({ _id: chat_id }, { title });
|
||||
}
|
||||
|
||||
|
||||
export function getChartsInMessage(message: OpenAI.Chat.Completions.ChatCompletionMessageParam) {
|
||||
if (message.role != 'assistant') return [];
|
||||
if (!message.tool_calls) return [];
|
||||
if (message.tool_calls.length == 0) return [];
|
||||
return message.tool_calls.filter(e => e.function.name === 'createComposableChart').map(e => e.function.arguments);
|
||||
}
|
||||
|
||||
export async function sendMessageOnChat(text: string, pid: string, initial_chat_id?: string) {
|
||||
|
||||
|
||||
@@ -100,43 +84,36 @@ export async function sendMessageOnChat(text: string, pid: string, initial_chat_
|
||||
await setChatTitle(text.substring(0, 110), chat_id);
|
||||
}
|
||||
|
||||
const userMessage: OpenAI.Chat.Completions.ChatCompletionMessageParam = {
|
||||
role: 'user', content: text
|
||||
}
|
||||
const userMessage: OpenAI.Chat.Completions.ChatCompletionMessageParam = { role: 'user', content: text }
|
||||
messages.push(userMessage);
|
||||
await addMessageToChat(userMessage, chat_id);
|
||||
|
||||
let response = await openai.chat.completions.create({ model: 'gpt-3.5-turbo', messages, n: 1, tools });
|
||||
let response = await openai.chat.completions.create({ model: OPENAI_MODEL, messages, n: 1, tools });
|
||||
|
||||
let responseMessage = response.choices[0].message;
|
||||
let toolCalls = responseMessage.tool_calls;
|
||||
const chartsData: string[][] = [];
|
||||
|
||||
await addMessageToChat(responseMessage, chat_id);
|
||||
messages.push(responseMessage);
|
||||
while ((response.choices[0].message.tool_calls?.length || 0) > 0) {
|
||||
await addMessageToChat(response.choices[0].message, chat_id);
|
||||
messages.push(response.choices[0].message);
|
||||
if (response.choices[0].message.tool_calls) {
|
||||
|
||||
console.log('Tools to call', response.choices[0].message.tool_calls.length);
|
||||
chartsData.push(getChartsInMessage(response.choices[0].message));
|
||||
|
||||
if (toolCalls) {
|
||||
console.log({ toolCalls: toolCalls.length });
|
||||
for (const toolCall of toolCalls) {
|
||||
const functionName = toolCall.function.name;
|
||||
const functionToCall = functions[functionName];
|
||||
const functionArgs = JSON.parse(toolCall.function.arguments);
|
||||
console.log('CALLING FUNCTION', functionName, 'WITH PARAMS', functionArgs);
|
||||
const functionResponse = await functionToCall({ pid, ...functionArgs });
|
||||
console.log('RESPONSE FUNCTION', functionName, 'WITH VALUE', functionResponse);
|
||||
messages.push({ tool_call_id: toolCall.id, role: "tool", content: JSON.stringify(functionResponse) });
|
||||
await addMessageToChat({ tool_call_id: toolCall.id, role: "tool", content: JSON.stringify(functionResponse) }, chat_id);
|
||||
for (const toolCall of response.choices[0].message.tool_calls) {
|
||||
const functionName = toolCall.function.name;
|
||||
console.log('Calling tool function', functionName);
|
||||
const functionToCall = functions[functionName];
|
||||
const functionArgs = JSON.parse(toolCall.function.arguments);
|
||||
const functionResponse = await functionToCall({ project_id: pid, ...functionArgs });
|
||||
messages.push({ tool_call_id: toolCall.id, role: "tool", content: JSON.stringify(functionResponse) });
|
||||
await addMessageToChat({ tool_call_id: toolCall.id, role: "tool", content: JSON.stringify(functionResponse) }, chat_id);
|
||||
}
|
||||
}
|
||||
response = await openai.chat.completions.create({ model: 'gpt-4o', messages, n: 1, tools });
|
||||
responseMessage = response.choices[0].message;
|
||||
toolCalls = responseMessage.tool_calls;
|
||||
|
||||
await addMessageToChat(responseMessage, chat_id);
|
||||
|
||||
response = await openai.chat.completions.create({ model: OPENAI_MODEL, messages, n: 1, tools });
|
||||
}
|
||||
|
||||
await addMessageToChat(response.choices[0].message, chat_id);
|
||||
await ProjectLimitModel.updateOne({ project_id: pid }, { $inc: { ai_messages: 1 } })
|
||||
|
||||
return responseMessage.content;
|
||||
return { content: response.choices[0].message.content, charts: chartsData.filter(e => e.length > 0).flat() };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getPlanFromId, getPlanFromTag } from '@data/PREMIUM';
|
||||
import { getPlanFromId, getPlanFromTag, PREMIUM_TAG } from '@data/PREMIUM';
|
||||
import Stripe from 'stripe';
|
||||
|
||||
class StripeService {
|
||||
@@ -133,9 +133,23 @@ class StripeService {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
async createOneTimeCoupon() {
|
||||
async createStripeCode(plan: PREMIUM_TAG) {
|
||||
if (this.disabledMode) return;
|
||||
if (!this.stripe) throw Error('Stripe not initialized');
|
||||
|
||||
const INCUBATION_COUPON = 'sDD7Weh3';
|
||||
|
||||
if (plan === 'INCUBATION') {
|
||||
await this.stripe.promotionCodes.create({
|
||||
coupon: INCUBATION_COUPON,
|
||||
active: true,
|
||||
code: 'TESTCACCA1',
|
||||
max_redemptions: 1,
|
||||
})
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async createOneTimeSubscriptionDummy(customer_id: string, planId: number) {
|
||||
|
||||
Reference in New Issue
Block a user